返回类型与函数重载

我们知道,函数重载是指在同一作用域内,可以有一组具有相同函数名,不同参数列表的函数,这组函数被称为重载函数

那么为什么我们不能通过返回类型的不同来进行函数重载?

因为在编译中,每一个函数都会按照一定的规则进行映射

test.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
namespace t{
float test(float i)
{
i = 1.2;
return i;
}

float test() {
return 1.2;
}
}

float test(float i) {
i = 3.4;
return i;
}

int test(int i)
{
i = 1;
return i;
}

float output(float i, int d) {
d = 3;
i = 1.4;
return i;
}

然后我们查看它的汇编代码

1
2
g++ -g -c test.cpp
objdump -S test.o > test_obj.s //可以和源代码一起看,酸爽!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
1.o:	file format Mach-O 64-bit x86-64

Disassembly of section __TEXT,__text:
__ZN1t4testEf:
; {
0: 55 pushq %rbp
1: 48 89 e5 movq %rsp, %rbp
4: f3 0f 10 0d 8c 00 00 00 movss 140(%rip), %xmm1
c: f3 0f 11 45 fc movss %xmm0, -4(%rbp)
; i = 1.2;
11: f3 0f 11 4d fc movss %xmm1, -4(%rbp)
; return i;
16: f3 0f 10 45 fc movss -4(%rbp), %xmm0
1b: 5d popq %rbp
1c: c3 retq
1d: 0f 1f 00 nopl (%rax)

__ZN1t4testEv:
; float test() {
20: 55 pushq %rbp
21: 48 89 e5 movq %rsp, %rbp
24: f3 0f 10 05 70 00 00 00 movss 112(%rip), %xmm0
; return 1.2;
2c: 5d popq %rbp
2d: c3 retq
2e: 66 90 nop

__Z4testf:
; float test(float i) {
30: 55 pushq %rbp
31: 48 89 e5 movq %rsp, %rbp
34: f3 0f 10 0d 64 00 00 00 movss 100(%rip), %xmm1
3c: f3 0f 11 45 fc movss %xmm0, -4(%rbp)
; i = 3.4;
41: f3 0f 11 4d fc movss %xmm1, -4(%rbp)
; return i;
46: f3 0f 10 45 fc movss -4(%rbp), %xmm0
4b: 5d popq %rbp
4c: c3 retq
4d: 0f 1f 00 nopl (%rax)

__Z4testi:
; {
50: 55 pushq %rbp
51: 48 89 e5 movq %rsp, %rbp
54: 89 7d fc movl %edi, -4(%rbp)
; i = 1;
57: c7 45 fc 01 00 00 00 movl $1, -4(%rbp)
; return i;
5e: 8b 45 fc movl -4(%rbp), %eax
61: 5d popq %rbp
62: c3 retq
63: 66 66 66 66 2e 0f 1f 84 00 00 00 00 00 nopw %cs:(%rax,%rax)

__Z6outputfi:
; float output(float i, int d) {
70: 55 pushq %rbp
71: 48 89 e5 movq %rsp, %rbp
74: f3 0f 10 0d 28 00 00 00 movss 40(%rip), %xmm1
7c: f3 0f 11 45 fc movss %xmm0, -4(%rbp)
81: 89 7d f8 movl %edi, -8(%rbp)
; d = 3;
84: c7 45 f8 03 00 00 00 movl $3, -8(%rbp)
; i = 1.4;
8b: f3 0f 11 4d fc movss %xmm1, -4(%rbp)
; return i;
90: f3 0f 10 45 fc movss -4(%rbp), %xmm0
95: 5d popq %rbp
96: c3 retq

从上面我们不难看出

  • __ZN1t4testEf int t::test (int)
  • __ZN1t4testEv: float t::tset(void)
  • __Z4testf : float test(float i)
  • __Z4testi : int test(int)
  • __Z6outputfi : float output(float, int)

根据上面的对比,我们大致可以推断出命名规则为作用域+函数名+参数类型

例如 : ZN1t4Z4 代表两个不同的作用域

outputZ6 output fi中的fi则表示的是参数类型,f表示float,i表示int

综上所述,映射后的函数名完全没有返回值什么事,当你调用一个函数,编译器首先要看看函数名列表,然后根据你的参数进行一个匹配

重载中最重要的就是对应函数的查找,为了正确查找函数,我们不能定义两个参数类型和函数名一样的函数,这是编译器生成的代码规则所决定的

如果你两个函数的作用域,函数名,参数类型都一样,那么也查找不到对应的函数了,如下(错误的代码)

1
2
3
4
5
6
//汇编之后的函数名(用下划线分割规则)
int test() {return 1;}
//__Zn_test_v
float test() {return 1.2;}
//__Zn_test_v
//两个都是一样的,二义性

这就是为什么不能仅仅通过返回值类型不同来进行重载