C++ | 函数重载
函数重载是什么
缺省参数让我们在参数数目不同时可以调用同一个函数(不清楚的可以看下我 C++ 零碎知识点集合 那篇博客),而函数重载让我们能够使用多个同名函数。
(其实写完这篇博客,觉得举这个例子不是很好..因为很多人并不是像我一样,先接触了 STL 再学习的 C++,看这篇博客的很多人应该并不理解 vector 之类的...然而已经写完了...)
第一次接触函数缺省参数应该是在使用 STL 的各种容器的时候,那时候还没开始学 C++,但是一直听说做题的时候容器很好用,就试着用了下。当时第一个用的是 vector,对于它的构造方法觉得惊为天人...
(其实这里举这个例子可能会有些听不懂...虽然可以再讲很多别的例子..但是对我印象最深刻的函数重载就是这个= =)
刚才特地去 cplusplus 上验证了下到底是重载还是参数缺省..事实证明就是重载..推荐大家多用这个网站..真的好用o
关于 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);
可能有人是第一次见两个引用符号...比如我...
下面具体解释下这三种参数对应的传参:
- 形参为 可变的左值;
- 形参为 可变的左值、不可变的左值 或 右值
- 形参为 右值(最后这个书上说 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++的结果:
可以看出,test 这一函数,在 c 与 c++ 汇编中的名字是不同的,c++ 对其进行的名称修饰,将原本的名字前面加了 4,表示函数名的长度为 4;test 后面加了 i 和 f ,表示第一个参数类型为 int ,第二个参数类型为 float。(当然,不同的编译器命名规则也不同)
因此也就可以解释,当我们的函数名相同时,参数类型不同,或其顺序不同时,参数列表也就不同了。
从中也可以看出,这在 c 中是不适用的,因为其未对函数名进行修饰,所有相同名字的函数,即使参数列表不同,在汇编中的名字是相同的。
所以,在链接这一阶段,便会报错,产生链接错误。
哈哈哈哈哈哈