- 重载。类的成员函数的函数名相同,但参数列表不同(类型、个数、顺序不同),就是函数的重载。C++编译器根据你调用函数时传入的不同的参数列表,去执行对应的那个函数。这点在c语言中是不能实现的,因为C语言没有这个语法特性,所以C编译器不支持,如果你这么写编译器肯定会给你报错无法编译通过。
重载
简单分析下,我猜测,重载在汇编中也没什么不同,肯定和普通函数一个样,有不同的函数标号,执行哪个重载函数,编译器就会Call 哪个函数。C++编译器会将每个成员函数都分别翻译成汇编并给它起个标号名,根据你要调用哪个具体的重载函数,去Call哪个汇编标号的函数。下面来分析下:
#include<iostream>
/**
* 继承:重写
*/
class Father
{
public:
int a;
virtual void add(){ // 不用virtual,子类只会函数隐藏,不会函数覆盖
a = -1;
}
};
class Son : public Father{
public:
int s;
void add() override{
a = 99;
}
};
int main(){
Father father;
father.add();
Son son;
son.add();
Son s;
Father f = s; // 发生对象切片,静态类型决定你调用哪个函数,父类对象调用父类的,子类对象调用子类的,因为只复制s中父类那部分数据到f,子类那部分直接丢弃
f.add(); // 调用父类的add()
Father &f1 = s; // 动态绑定,必须用引用或指针才会动态绑定,不会发生对象切片
f1.add(); //调用子类add()
// std::cout<<son.a << ", "<< father.a<< std::endl ;
return 0;
}
类中有3个重载函数add,函数名一样,参数不一样,分别调用,看看编译成汇编是怎么实现重载特性的:
main:
.LFB1525:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp # 分配16字节栈帧给main函数
movq %fs:40, %rax
movq %rax, -8(%rbp)
xorl %eax, %eax
leaq -12(%rbp), %rax # 算出rbp-12这个值
movq %rax, %rdi
call _ZN3Son3addEv # 调用add()
leaq -12(%rbp), %rax
movl $33, %esi
movq %rax, %rdi
call _ZN3Son3addEi # 调用add(int i)
leaq -12(%rbp), %rax
movl $78, %edx
movl $10, %esi
movq %rax, %rdi
call _ZN3Son3addEii # 调用add(int i, int j)
movl $0, %eax
movq -8(%rbp), %rcx
xorq %fs:40, %rcx
je .L6
call __stack_chk_fail@PLT
.L6:
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
果然不出我们所料,即使复杂的重载概念,也只是编译器层面的抽象概念,真正体现到硬件、cpu指令、内存分配与读写时,其实依旧是C语言汇编那点事情,无非就是内存在哪分配,它的地址怎么计算,然后传参,Call函数标号。通过这里,可以更加深刻体会到,所谓C++的语法特性在汇编层面的实现,其实就是C语言汇编的各种组合,包括内存分配、地址计算、变量的字节长度计算、函数调用、参数传递等等。
当然,以上只是原理上并没有很绕的地方,但具体实现,要考虑的细节很多,真正实现一个可靠的编译器计算这些内存分配、地址计算等,要做大量工作,因此才会说现代C++编译器代码的复杂程度超过linux内核源码。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。