C++ | 函数重载

函数重载是什么

缺省参数让我们在参数数目不同时可以调用同一个函数(不清楚的可以看下我 C++ 零碎知识点集合 那篇博客),而函数重载让我们能够使用多个同名函数


(其实写完这篇博客,觉得举这个例子不是很好..因为很多人并不是像我一样,先接触了 STL 再学习的 C++,看这篇博客的很多人应该并不理解 vector 之类的...然而已经写完了...)

第一次接触函数缺省参数应该是在使用 STL 的各种容器的时候,那时候还没开始学 C++,但是一直听说做题的时候容器很好用,就试着用了下。当时第一个用的是 vector,对于它的构造方法觉得惊为天人...
(其实这里举这个例子可能会有些听不懂...虽然可以再讲很多别的例子..但是对我印象最深刻的函数重载就是这个= =)

刚才特地去 cplusplus 上验证了下到底是重载还是参数缺省..事实证明就是重载..推荐大家多用这个网站..真的好用o

vector

关于 const allocator_type& alloc 这个参数我们暂时不用管(百度了下是模板..然而没学过..只能暂时不管了...),只需要看其他的即可 。
这里声明了几个的 vector 的构造函数,从这也能看出传参方式不同:

1. 无参数;
2. 传一个 n 来设定 vector 的初始元素个数;
3. 传 n 设定元素个数,传 value 设定每个元素的初始值;
4. 是拷贝构造,传入一个 vector 的对象;
5. 传两个迭代器,设定第一个元素的地址与最后一个元素的下一位置的地址。

暂时只能看懂以上构造函数,但是也可以说明一个问题,那就是,当我们传入的参数不同时,所调用的函数也会不同

构造方法

这里是几个构造方法的例子,分别对应了我上面所举的例子的 1、3、5、4 这四种方法。

到这里有没有稍微明白一点函数重载到底是什么呢?

函数重载即,我们设计一系列函数——他们有相同的函数名,并且完成相同的工作,但有不同的参数列表。

C++ 相对于 C 引入了函数重载,很方便的解决了我们很多问题,就像之前那个 vector 的几种构造方法一样,他们略微有一些差异,但最终实现的功能是一样的,我们可以根据他们的功能来记住这一类函数的名字,但若是每个函数都有不同的名字,调用时便会很不方便。

可以构成重载的要求

参数列表的要求

但不是所有函数都可以重名的。函数重载也有前提:参数列表不同
函数的参数列表也成为函数特征标。他由三个特性构成:

1. 参数数目和类型
2. 参数的排列顺序

也就是说,有两个函数,在这两点中有任意一点不同,我们称其参数列表(特征标)不同。

注意,我们所说的是要求参数列表(特征标)不同,而不是函数类型不同,以下这两个声明是互斥的:

int test(int x);
double test(int x);

但是当参数列表不同时,返回类型可以不同:

int test(int x);
double test(float x);

总结:需要使用函数重载,其参数列表必须不同,而返回类型可不同也可相同(具体原因后面会讲)。

其他注意点

1、类型需要完全匹配

在使用时有很多地方需要我们注意:

	void test(int a); 
	void test(float f);

这时候如果调用:

double t = 1;
test(t);

当 test 函数没有重载时,传入参数类型若不匹配,编译器便会将类型强转,然而现在有了两种转换 t 的方式,编译器便会将其视为错误。

2、注意引用

另外,有些看起来彼此不同的参数列表是不能共存的:

void test(int x);
void test(int& x);

的确,您可能认为他们参数列表不同,可是换一种考虑方式:当您调用

    int a = 1;
    test(a);

这时候两个函数都匹配,又让编译器去调用哪个呢?为了避免这种情况,编译器在检查函数特征标时,将类型本身与类型引用视为同一特征标。

3、const 指针类型匹配

看下面一个简单的例子:

void test(const char* s) {
    cout << "test1" << endl;
}

void test(char* s) {
    cout << "test2" << endl;
}

int main() {
    const char a[] = "hello";
    char b[] = "hello";
    test(a);
    test(b);
    return 0;
}

输出结果:

test1
test2

可以看出,const 修饰的参数会与 const 类型匹配,而非 const 参数与非 const 类型匹配,但是注意,若不是指针,而是普通变量,编译器则会报错:

void test(int x);
void test(const int x);

上面的代码编译器会提示你函数重定义,说明编译器并不区分const变量与非const变量(我的猜测是,无论是不是const变量,对于原变量,都不会修改,因为参数是原本参数的一份拷贝,因此,除指针和引用外的变量,是不是const变量都无所谓)。

重载引用参数

在C++中经常用到引用,因此重载引用参数也值得探讨。

void test(double & x);
void test(const double & x);
void test(double && x);

可能有人是第一次见两个引用符号...比如我...
下面具体解释下这三种参数对应的传参:

  1. 形参为 可变的左值;
  2. 形参为 可变的左值、不可变的左值 或 右值
  3. 形参为 右值(最后这个书上说 rvalue,但翻译又说是左值,怀疑是写错了)。

可以发现,可变的左值既可匹配 1 也可匹配 2;右值既可匹配 2 又可匹配 3;那么到底如何匹配呢?
答案是:选择最匹配的。
看下面一个对比就明白了:

void test(double& x) {
    cout << "test1" << endl;
}

void test(const double& x) {
    cout << "test2" << endl;
}

void test(double&& x) {
    cout << "test3" << endl;
}

int main() {
    double a = 1;
    const double b = 2;
    test(a);
    test(b);
    test(a + b);
    return 0;
}

答案:

test1
test2
test3

这里 a 为左值,所以匹配最匹配的 test1,b 为不可变的左值,所以匹配 test2, a+b 为右值,所以匹配最匹配的 test3。而下一个例子则不是:

void test(const double& x) {
    cout << "test2" << endl;
}

int main() {
    double a = 1;
    const double b = 2;
    test(a);
    test(b);
    test(a + b);
    return 0;
}

答案:

test2
test2
test2

这里的三个答案均为 test2,是因为 a 和 a+b 没有最原本那两个选择,最匹配的变为了 test2。这就是选择最匹配的意思。

何时采用函数重载

当我们的函数将执行相同的任务,但使用不同形式的数据时,可采用函数重载。当然,并不是所有时候都需要用到函数重载,有些地方用缺省参数会更方便些。

函数重载的原理

想要明白函数的原理,就需要先明白名称修饰(名称矫正)。什么是名称修饰呢?
我们的编译器在编译阶段,将函数名进行了修饰,将其从原本的函数名,变为了修饰后的修饰名。在调用函数的时候,并不是按函数名去直接调用函数的,而是按照修饰名去调用函数。
那么C++是如何对函数名进行修饰的呢?来看一个例子:

void test(int a, float b) {
	// ...
}

int main() {
	test(1, 2);
	return 0;
}

一段简单的代码,我们分别在 c 和 c++ 中运行,查看其汇编代码。
linux 下执行以下语句来查看:

objdump -D a.out | grep "test"
a.out:是我生成的可执行文件名,如果不是这个名字,可修改下;
grep:由于汇编代码有点长..我就写了辣么几行代码就有好多啊..干脆就通过管道找起来方便点...

c的结果:
c
c++的结果:
c++

可以看出,test 这一函数,在 c 与 c++ 汇编中的名字是不同的,c++ 对其进行的名称修饰,将原本的名字前面加了 4,表示函数名的长度为 4;test 后面加了 if ,表示第一个参数类型为 int ,第二个参数类型为 float。(当然,不同的编译器命名规则也不同)
因此也就可以解释,当我们的函数名相同时,参数类型不同,或其顺序不同时,参数列表也就不同了。

从中也可以看出,这在 c 中是不适用的,因为其未对函数名进行修饰,所有相同名字的函数,即使参数列表不同,在汇编中的名字是相同的。
所以,在链接这一阶段,便会报错,产生链接错误。

哈哈哈哈哈哈