C++学习:默认参数、函数重载、函数模板

一、默认参数

默认参数是C++中新引入的内容。

默认参数指的是,当函数调用中省略了实参时,函数会自动使用默认的参数。

要想使用默认参数,就必须得通过函数原型。因为编译器是通过函数原型爱了解函数所用的参数 数量,因此函数原型必须将可能得默认参数告诉程序。方法如下,left()函数的原型:

char* left(const char* str, int n=1);

上述函数是希望返回一个新的字符串,因此将返回类型设置为char*,也就是指向char的指针;

因为希望原始字符串保持不变,函数的第一个参数使用const限定符;

因为希望n的默认值为1,就讲n赋值1,如果调用函数left()省略了参数n,那函数就将n设置为1;

对于带参数列表的函数,必须从右往左添加默认值,也就是说,如果要为某个参数设置默认值,那你就得保证,它右边的所有参数都已经提供了默认值:

int harpo(int n, int m=4, int j=5); //合法的操作

int groucho(int k=1, int m=2, int n=3);//合法的操作


int chico(int n, int m=6, int j);//非法的操作,右边还有参数没有初始化

上面的harpo()函数原型允许调用该函数时,提供1个、2个、或3个默认参数

beeps = harpo(2); //相当于使用harpo(2, 4, 5)
beeps = harpo(1,8); //相当于使用harpo(1, 8, 5)
beeps = harpo(8,7,6); //没有使用默认参数

实参按从左到右的顺序一次被赋给相应的形参,不能跳过任何参数。

默认参数不算变成方面的重大突破,只是提供了一种便捷的方式。使用默认参数,可以减少要定义的析构函数。

程序实例1:

#include <iostream>

using namespace std;

const int ArSize = 80;
char* left(const char* str, int n = 1);


int main()
{
    char sample[ArSize];
    cout << "enter a string:\n";
    cin.get(sample,ArSize);

    char* ps = left(sample, 7);
    cout << ps << endl;
    delete [] ps;

    ps = left(sample);
    cout << ps << endl;
    delete [] ps;

    return 0;
}


char* left(const char* str, int n)
{
    if(n < 0)
        n = 0;
    char* p = new char [n+1];
    int i;

    for(i=0; i<n && str[i]; i++)
    {
        p[i] = str[i];
    }

    while(i<=n)
    {
        p[i++] = '\0';
    }

    return p;
}

输出结果:

enter a string:
hello world
hello w
h

二、函数重载(Overload)

函数重载,是C++在C语言的基础上新增的功能。默认参数让程序能够使用不同数目的参数调用同一个函数,而函数重载能够使用多个同名的函数。术语“函数重载”指的计就是可以有多个同名的函数。函数重载就像就像一个动词有多重含义,比如root,可以是助威的意思,也可以是种植的意思,具体表示什么意思,就要看它的使用场景。

函数重载的关键就是函数的参数列表,参数列表页称为特征标。如果两个函数的参数数目和类型都相同,参数的排列顺序也想要,则它们的特征标是相同的,特征标与变量的名称无关。

C++允许定义名称相同的函数,是有前提的,函数的特征标要不一样。

重载函数的表现形式以及条件

以下三个条件,只要满足其中的一个就表示重载函数:

1、函数的参数个数不同;

2、函数参数的类型不同;

3、函数参数的顺序不同

程序实例2:函数名与不同的参数搭配时,函数的含义是不一样的,表示不同的函数

#include<stdio.h>
#include<string.h>


int func(int x)
{
	return x;
}


int func(int a, int b)
{
	return a + b;
}


int func(const char* s)
{
	return strlen(s);
}


int main()
{
	printf("%d\n", func(3)); //输出3
	printf("%d\n", func(4,5));//输出9
	printf("%d\n", func("helloworld!"));//输出11

	return 0;
}

编译器调用重载函数的规则

1、将所有同名函数作为候选项;

2、尝试寻找可行的候选参数

        精确匹配实参

        通过默认参数能够匹配实参

        通过默认类型转换匹配实参

3、匹配失败的情况

        最终的候选函数有两个及以上,就编译失败;

        没有候选函数,编译失败。

函数重载的注意事项

1、函数重载是由函数名参数列表决定的

2、重载函数本质上是相互独立的不同函数;

3、重载函数的函数类型是不一样的;

4、函数的返回值不能作为函数重载的依据

函数重载的本质分析

程序实例3:函数重载的本质分析

#include<stdio.h>
#include<string.h>

//函数类型:int(int, int, int)
int add(int a, int b, int c)
{
	return a + b + c;
}

//函数类型:int(int,int)
int add(int a, int b)
{
	return a + b;
}


int main()
{
	printf("%d\n", add(1, 2)); //调用int add(int a, int b)
	printf("%p\n",(int(*)(int, int))add);//打印函数的入口地址

	printf("\n");

	printf("%d\n", add(1, 2, 3));//调用int add(int a, int b, int c)
	printf("%p\n", (int(*)(int, int, int))add);//打印函数的入口地址

	return 0;
}

输出结果:

3
00A513CF

6
00A513CA

从打印的函数地址来看,函数add(int a, int b, int c)与函数add(int a, int b)的地址是不一样的,表示不同的函数。

重载函数与函数指针

将重载函数的函数名赋值给函数指针时:

        根据重载规则挑选与函数指针参数列表一致的候选者;

        严格匹配候选者的函数类型和函数指针的函数类型;

程序实例4:根据函数指针的参数列表去对应重载函数

#include<stdio.h>
#include<string.h>


int func(int x)
{
	return x;
}


int func(int a, int b)
{
	return a + b;
}


int func(const char* s)
{
	return strlen(s);
}

typedef int(*PFUNC) (int a);


int main()
{
	int c = 0;

	PFUNC p = func;

	c = p(2); //函数指针去匹配函数

	printf("c = %d\n",c);

	return 0;
}

输出结果:

c = 2

从输出的结果知道p(2),相当于调用了函数int func(int x)。

什么时候使用函数重载

虽然函数重载很吸引人,但是不要滥用。只有当函数基本上执行相同的任务,但使用不同形式的数据时,才应该使用重载。

三、函数模板

函数模板也是C++新增的一项特性,函数模板==通用函数,有了函数模板就可以进行泛型定义函数了。

可参考数据结构学习:泛型编程(函数模板与类模板)_资深流水灯工程师的博客-CSDN博客

我们知道交换两个int型变量的值,可以使用下面的代码实现

void swap(int& a, int& b)
{
    int temp;
    
    temp = a;
    a = b;
    b = temp;
}

如果要交换两个float类型变量的值呢,改一下代码就是的

void swap(float& a, float& b)
{
    float temp;
    
    temp = a;
    a = b;
    b = temp;
}

如果要交换两个char呢,double呢都要这么改下去吗?也不是不可以,但是C++中有更简单的方法。那就是函数模板,使用函数模板,既可以节省时间,还更可靠。代码可以是这样的

template <typename T>
void swap(T& a, T& b)
{
    T temp;
    
    temp = a;
    a = b;
    b = temp;
}

需要使用关键字template和关键字typename,就像上面代码的写法,至于那个T,大家都是这么勇的,你也可以用其他你想用的字母或单词。我还是用T,这个T可以代表任何类型。

#include <iostream>

using namespace std;

template<typename T>
void exchange(T& a, T& b);


int main()
{

    int i = 10;
    int j = 20;
    cout << "i = " << i << endl;
    cout << "j = " << j << endl;

    exchange(i,j);
    cout << "i = " << i << endl;
    cout << "j = " << j << endl;

    double x = 24.5;
    double y = 81.7;
    cout << "x = " << x << endl;
    cout << "y = " << y << endl;

    exchange(x,y);
    cout << "x = " << x << endl;
    cout << "y = " << y << endl;


    return 0;
}

template<typename T>
void exchange(T& a, T& b)
{
    T temp;

    temp = a;
    a = b;
    b = temp;
}

输出结果:

i = 10
j = 20
i = 20
j = 10
x = 24.5
y = 81.7
x = 81.7
y = 24.5

函数模板不能缩短可执行程序,上面的代码最终还是两个独立的函数定义。

模板重载

需要对多个不同类型使用同一种算法的时候,可以使用模板,但是并非所有的类型都是使用相同的算法,这就需要像重载函数一样重载模板。下面的程序实例新增一个交换模板,原先模板的特征标是(T&,T&),新模板的特征标是(T&, T&, n);

程序示例:

#include <iostream>

using namespace std;

template<typename T>
void exchange(T& a, T& b);

template<typename T>
void exchange(T a[], T b[], int n);

void show(int a[]);
const int Lim = 8;


int main()
{

    int i = 10;
    int j = 20;
    cout << "i = " << i << endl;
    cout << "j = " << j << endl;

    exchange(i,j);
    cout << "i = " << i << endl;
    cout << "j = " << j << endl;

    int d1[Lim] = {0,7,0,4,1,7,7,6};
    int d2[Lim] = {0,7,2,0,1,9,6,9};
    show(d1);
    show(d2);
    exchange(d1,d2,Lim);
    show(d1);
    show(d2);


    return 0;
}

template<typename T>
void exchange(T& a, T& b)
{
    T temp;

    temp = a;
    a = b;
    b = temp;
}

template<typename T>
void exchange(T a[], T b[], int n)
{
    T temp;

    for(int i=0; i<n; i++)
    {
        temp = a[i];
        a[i] = b[i];
        b[i] = temp;
    }

}

void show(int a[])
{
    cout <<a[0] <<a[1]<<"/";
    cout <<a[2] <<a[3]<<"/";

    for(int i=4;i<Lim;i++)
        cout << a[i];
    cout << endl;
}

输出结果:

i = 10
j = 20
i = 20
j = 10
07/04/1776
07/20/1969
07/20/1969
07/04/1776

显示具体化

假设定义了下面的结构体

struct job
{
    char name[40];
    double salary;
    int floor;
};

如果希望交换两个结构体的内容,可以使用下面的代码来完成

    temp = a;
    a = b;
    b = temp;

由于C++允许将一个结构体赋值给另一个结构体,即使T是一个job结构体类型,上面的代码也是适用的。

假设要只想交换salary和floor成员,不交换name成员,则需要使用不同的代码,这时候就需要提供一个具体化函数定义,称为显示具体化,其中包含所需的代码,当编译器找到与函数调用匹配的具体化定义时,就使用该定义,而不寻找模板。

对于给定的函数名,可以有非模板函数、模板函数、显示具体化模板函数、以及它们的重载版本;

显示具体化的原型和定义以template<>开始,并通过名称来指出类型;

具体化优先于常规模板,非模板函数优先于具体化的原型;

#include <iostream>

using namespace std;


template<typename T>
void exchange(T& a, T& b);

struct job
{
    char name[40];
    double salary;
    int floor;
};

template<> void exchange<job>(job &j1, job &j2);


void show(job& j);

int main()
{
    cout.precision(2);
    cout.setf(ios::fixed, ios::floatfield);

    int i = 10, j =20;
    cout << "i, j = " << i << ", " << j << ".\n";

    exchange(i,j);//调用exchange(int&, int&)
    cout << "after exchange i, j = " << i << ", " << j << ".\n";

    job sue = {"susan", 73000.60, 7};
    job sidney = {"sidney", 78060.70, 9};
    cout << "before job exchange: \n";
    show(sue);
    show(sidney);

    exchange(sue, sidney); //调用exchange(job&, job&)
    cout << "after job exchange: \n";
    show(sue);
    show(sidney);

    return 0;
}

template<typename T>
void exchange(T& a, T& b)
{
    T temp;

    temp = a;
    a = b;
    b = temp;
}

//显示具体化,如果exchange函数的参数是job类型就调用这个函数
template<> void exchange<job>(job &j1, job &j2)
{
   double t1;
   int t2;

   t1 = j1.salary;
   j1.salary = j2.salary;
   j2.salary = t1;

   t2 = j1.floor;
   j1.floor = j2.floor;
   j2.floor = t2;

}

void show(job& j)
{
    cout << j.name << " : $" << j.salary << " on floor " << j.floor << endl;
}

输出结果:

i, j = 10, 20.
after exchange i, j = 20, 10.
before job exchange:
susan : $73000.60 on floor 7
sidney : $78060.70 on floor 9
after job exchange:
susan : $78060.70 on floor 9
sidney : $73000.60 on floor 7

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值