简略重述下重载与重写的区别:
- 重载。类的成员函数的函数名雷同,但参数列表不同(类型、个数、程序不同),就是函数的重载。C++编译器依据你调用函数时传入的不同的参数列表,去执行对应的那个函数。这点在c语言中是不能实现的,因为C语言没有这个语法个性,所以C编译器不反对,如果你这么写编译器必定会给你报错无奈编译通过。
- 重写。即笼罩,子类的某个成员函数A的返回值、函数名和参数列表和父类某个成员函数A截然不同,那么子类就把父类的A函数给笼罩掉了,当前调用子类的A函数,执行的是子类A函数的逻辑,如果在子类中不重写,则将会调用父类的A函数。
重载
简略剖析下,我猜想,重载在汇编中也没什么不同,必定和一般函数一个样,有不同的函数标号,执行哪个重载函数,编译器就会Call 哪个函数。C++编译器会将每个成员函数都别离翻译成汇编并给它起个标号名,依据你要调用哪个具体的重载函数,去Call哪个汇编标号的函数。上面来剖析下:
#include<iostream>/** * 继承:重载*/class Son{public: int a; void add(){ a = 0; } void add(int i){ a = i; } void add(int i, int j){ a = i+j; }};int main(){ Son son; son.add(); son.add(33); son.add(10, 78); 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内核源码。
重写
间接上代码:
#include<iostream>/** * 继承:重写*/class Father{public: int a; void add(){ a = -1; }};class Son : public Father{public: int s; void add(){ a = 99; }};int main(){ Father father; father.add(); Son son; son.add(); Father f = Son(); f.add(); return 0;}
对应的汇编是:
main:.LFB1524: .cfi_startproc endbr64 pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 subq $32, %rsp movq %fs:40, %rax movq %rax, -8(%rbp) xorl %eax, %eax leaq -24(%rbp), %rax movq %rax, %rdi call _ZN6Father3addEv # father.add() 调用的是父类的add() leaq -16(%rbp), %rax movq %rax, %rdi call _ZN3Son3addEv # son.add() 调用的是子类的add() movl $0, -20(%rbp) leaq -20(%rbp), %rax movq %rax, %rdi call _ZN6Father3addEv # Father f = Son(); f.add(); 调用的是父类的add() movl $0, %eax movq -8(%rbp), %rdx xorq %fs:40, %rdx je .L5 call __stack_chk_fail@PLT.L5: leave .cfi_def_cfa 7, 8 ret .cfi_endproc
由此看出,重写,体现在汇编上,也只是C语言汇编中的函数调用和参数传递那一套流程,只不过,到底该调用谁,是C++编译器计算好了,也就是说,C++编译器把这种形象隐含的到底该调哪个函数的逻辑工作,曾经在编译时做完了,它给你的,也只是简略的汇编指令。