深入理解汇编/C语言
2024-09-22 10:16:43 1 举报
AI智能生成
"深入理解汇编/C语言"是一本关于汇编语言和C语言的权威书籍,它旨在帮助读者深入理解这两种低级和高级编程语言。汇编语言是一种低级语言,它直接操作计算机的硬件,而C语言是一种高级语言,它更接近人类理解的自然语言。通过这本书,读者可以学习到汇编语言的基本语法和指令,以及C语言的数据类型、控制结构、函数和指针等核心内容。此外,它还详细介绍了如何将汇编语言和C语言结合起来,以提高程序的执行效率和可移植性。这本书适用于计算机科学、电子工程和软件开发等相关专业的学生和从业者,是一本极具参考价值的工具书。
作者其他创作
大纲/内容
执行流是什么?执行流就是一段逻辑上独立的指令区域,对应于代码,大到可以是整个正序文件,即进程,小到可以是小到功能独立的代码块,即函数,而线程本质上就是函数。执行流是独立的,其独立性体现再每个执行流都有自己的占、一套自己的寄存器映像和内存资源,其实这就是执行流的上下文环境。因此,我们要想构造一个执行流,就要为其提供一整套的资源。任何代码块,无论大小都可以独立成为执行流,只要在它运行的时候,我们提前准备好它所依赖的上下文环境即可,这里的上下文环境就是它所使用的寄存器映像、栈、内存等资源1.使用Java、C++写程序,基本单位是类的方法2.使用C语言写程序,基本单位是函数3.使用汇编写程序,基本单位就称为执行流(CPU执行引擎执行程序也称为执行流)
执行流的作用是什么? 那么,成为独立的执行流有什么用呢?在任务调度器的眼里,只有执行流才是调度单元,即处理器上运行的,每个任务都是调度器给分配的执行流,只要成为执行流就能够独立在处理器上运行了,也就是说可以分配处理器的时间,处理器会专门分时来处理这个执行流的指令。
扩展知识:执行流,通常在计算机科学和软件工程领域中指的是程序执行的顺序和过程。在不同的技术背景下,执行流的概念可以有所不同。下面是几个常见的角度解释执行流的概念:1.程序执行流在程序设计中,执行流值得是程序代码从开始到结束的执行顺序.它遵循程序的逻辑结构,如顺序结构、选择结构(条件判断)和循环结构。程序执行流按照代码编写的顺序执行,除非遇到分支语句(如if-else)或循环语句,这会导致控制流转移到其他代码块2.数据流在数据处理和数据流编程中,执行流关注的是数据在程序中的移动和变换过程。例如,在流式处理中,数据元素按照一定的顺序依次经过多个处理阶段,每个阶段对数据进行转换或操作3.并发执行流在并发编程中,执行流涉及到多个任务或线程的执行顺序。操作系统或执行环境会调度这些任务或线程,使得它们能在单个处理器上以某种方式交错执行,从而提高效率4.事务执行流在数据库管理系统中,事务执行流是指事务中各个操作的执行顺序。事务是一些列操作,要么全部成功,要么全部失败,这种执行顺序确保了事务的原子性5.业务流程执行流在业务流程管理中,执行流定义了业务流程中各个任务或活动的执行顺序。业务流程执行语言(如BPEL)允许定义复杂的工作流,以自动化商业过程在上述的每一个场景中,执行流都是确保程序或系统正确、高效运行的关键。开发者必须仔细考虑执行流的逻辑,以确保程序的行为符合预期,尤其在并发或分布式系统中,执行流的控制和管理尤为重要
执行流
硬编码(Hardcoding)硬编码是指将具体的数值、路径、参数等直接写入程序代码中,而不通过变量或配置文件来表示。这样的做法使得程序中的这些数值和参数变得固定,不容易修改,且缺乏灵活性。硬编码的值通常被称为\"魔法数(Magic Numbers)\"或\"魔法字符串\",因为它们没有直观的含义,只能通过查看代码来了解。例如,以下是一个硬编码的实例,其中数值10直接出现在代码中```javafor i in range(10): print(\"Iteration\
软编码(Softcoding)软编码是指通过变量、配置文件、参数等方式将具体数值或参数抽象出来,而不直接写入代码。通过软编码,程序变得更加灵活,可以更容易地进行修改和维护,且适应性更强。使用软编码的例子:```javaiterations = 10;for i in range(iterations): print(\"Iteration\
总结硬编码:将具体数值、参数等直接写入程序代码中,缺乏灵活性,不易修改和维护软编码:通过变量、配置文件等方式将数值或参数抽象出来,使得程序更具灵活性,易于修改和维护
硬编码
汇编语言是什么汇编语言(Assembly Language)是一种用于电子计算机、微处理器、微控制器或其他可编程器件的低级语言,亦称为符号语言。在汇编语言中,用助记符(Mnemonics)代替机器指令的操作码,用地址符号(Symbol)或标号(Label)代替指令或操作数操作数的地址。在不同的设备中,汇编语言对应着不同的机器语言指令集,通过汇编过程转换成机器指令。普遍地说,特定地汇编语言和特定的机器语言指令集是一一对应的,不同平台之间不可直接移植。许多汇编程序为程序开发、汇编控制、辅助调试提供了额外的支持机制。有的汇编语言编程工具经常会提供宏,它们也被称为宏汇编器。汇编语言不像其他大多数的程序设计语言一样被广泛用于程序设计。在今天的实际应用中,它通常被应用在底层,硬件操作和高要求的程序优化的场合。驱动程序、嵌入式操作系统和实时运行程序都需要汇编语言。Microsoft宏汇编器(称为MNASM)是Windows下常用的汇编器GAS(GNU汇编器)和NASM是两种基于Linux的汇编器。其中NASM的语法与MASM的最相似。ARM汇编汇编语言是最古老的编程语言,在所有的语言中,它与原生机器语言最为接近。它能直接访问计算机硬件,要求用户了解计算机架构和操作系统MASM能创建哪些类型的程序?32位保护模式(32-Bit Protected Mode):32位保护模式程序运行于所有的32位和64位版本的Mircrosoft Windows系统。它们通常比实模式更容易编写和理解。从现在开始,将其简称为32位模式。64位模式(64-Bit Mode):64位程序运行于所有的64位版本Microsoft Windows系统16位实地址模式(16-Bit Real-Address Mode):16位程序运行于32位版本Windows和嵌入式系统。64位Windows不支持这类程序
什么是汇编器和链接器?汇编器(assembler)是一种工具程序,用于将汇编语言源程序转换为机器语言。链接器(linkder)也是一种工具程序,它把汇编器生成的单个文件组合为一个可执行程序。还有一个相关的工具,称为调试器(debugger),使得程序员可以在程序运行时,但不执行程序并检查寄存器和内存状态。编译器分为gcc、g++调试器分为:gdb、lldb、kgdb,其中gdb和lldb是应用层调试器,kgdb+qemu是Linux内核层调试器,Windows下的内核调试器是windbg
汇编语言与机器语言有关系?机器语言(machine language)是一种数字语言,专门设计成能被计算机处理器(CPU)理解。所有x86处理器都理解共同的机器语言。汇编语言(assembly language)包含用短助记符如ADD、MOV、SUB和CALL书写的语句。汇编语言与机器语言是一对一(one-to-one)的关系,每一条汇编语言指令对应一条机器语言指令。寄存器(register)是CPU种被明明的存储为止,用于保存操作的中间结果C++和Java与汇编语言有什么关系?高级语言如Python、C++和Java与汇编语言和机器语言的关系是一对多(one-to-many)。比如,C++的一条语句就会扩展为多条汇编指令或机器指令汇编语言可移植吗?一种语言,如果它的源程序能够在各种各样的计算机系统种进行编译和运行,那么这种语言被称为是可移植的(portable).例如,一个C++程序,除非需要特别引用某种操作系统的库函数,否则它就几乎可以在任何一台计算机上编译和运行。Java语言的一大特点就是,其编译好的程序几乎能在所有计算机系统种运行。汇编语言不是可移植的,因为它是为特定处理器系列设计的。比如Intel、AMD。目前广泛使用的有多种不同的汇编语言,每一种都基于一个处理器系列。
汇编语言
OPP和OOP面向过程是最为实际的一种思考方式,就算是面向对象的方法也是含有面向过程的思想。可以说面向过程是一种基础的方法。它考虑的是实际的实现。一般的过程是从上往下步步求解,所以面向过程最重要的是模块化的思想方法。当程序规模不是很大时,面向过程的方法还会体现出一种又是。因为程序的流程很清楚,按着模块与函数的方法可以很好的组织。面向对象是基于对象概念,以对象为中心,以类和继承为构造机制,来认识、理解、刻画客观世界和设计、构建相应的软件系统。类和继承是适应人们一般思维方式的描述范式。方法是允许作用于该类对象上的各种操作。这种对象、类、消息和方法的程序设计范式的基本点在于对象的封装性和类的继承性。通过封装能将对象的定义和对象的实现分开,通过继承能体现类与类之间的关系,以及由此带来的动态联编和实体的多态性
举个例子比如说完成\"吃饭\"这个任务如果是人吃肉,则eat(人,肉);如果是猫吃鱼,则eat(猫,鱼);eat是人和猫共用的吃饭本能,那如果之后要处理鱼吃虾、奥特曼池小怪兽呢?eat函数中就会存在大量的if-else的判断,这段代码,无疑是很恶心的。如果是面向对象思想,如何来解决这个问题呢?我们发现,人、猫、鱼、奥特曼,都有一个\"吃\"的共性。我么你抽象出每个受体的类,然后继承,这样都具有\"吃\
AOP面向切面编程主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。那么,AOP如何体现?这里可以联想一下laravel的中间件、javaweb的拦截器、vue的Decorator...它们都是AOP思想的时间。装饰器模式、代理模式,它们也是基于AOP思想的设计模式。AOP思想,指导我们通过找到平整切面的形式,插入新的代码,使新插入的代码对切面上下原有流程的伤害降到最低。
举个例子我们拿(PHP的Web框架)laravel中间件做什么?权限、日志、请求过滤、请求频率限制、csrf过滤....我们知道,中间件对controller的业务逻辑,不会有任何伤害,如果没有这个切面,我们想要记录请求日志,可能需要在每个controller的具体方法中写日志记录的代码,或者调用日志记录的函数、方法。这会使一段记录代码的日志,或调用记录日志的调用语句出现在许多controller中,这与controller原本要关注的逻辑无关,使controller职责不再单一,提高维护成本。当然,我们可能会写一个父类,让许多controller来继承这个父类,然后统一在父类的constructor方法中记录日志,以此来解决耦合问题。但实际上,这个父类的constructor方法,不正是一个切面吗?它在原有流程中截取了一个切面,在切面中植入代码,以达到承上启下的作用,并且不对上下文产生伤害。从这个例子中,我们也能得出另外一个思考:AOP知道我们寻找切面,但找到合适的切面,也尤为重要,就像上文,父类构造方法的切面和中间件的切面比起来,显然中间件这个切面更利于维护,你可以灵活选择中间件,但你无法灵活选择父类,因为决定你的controller继承什么父类的,不是切面中的代码,而是controller本身处理什么逻辑。许多项目,OPP、OOP、AOP使同时存在的,它们是编程范式,是一种指导编程的思想,并非不能互相配合
C++中在结构体中可以定义函数,前提是在g++编译器的支持下
C中结构体中定义函数指针
C语言真的不能面向对象吗?一直依赖,有关于C++、Java、C#等语言的书总喜欢在开篇介绍中拿C语言来比较一番,在承认C语言无可争议的运行效率的同时,也总爱拿C语言不具备面向对象血统的短板说事儿。难道C语言真的不具备面向对象的能力吗?考虑这个问题之前,首先要明确一点,什么是面向对象,或者说什么是对象?可以认为,对象=属性+行为。这跟那著名的公式\"算法+数据结构=程序\"没什么本质区别,但是两者的侧重点不同,抽象层次也不同。面向对象强调的是以数据为核心,在程序中复活事物的抽象本质,让它们\"智能地\"参与问题的求解,而不是传统的以算法或函数过程为核心去解决问题。所以面向对象绝不只是一种技术,更重要的是一种思想,是一种考虑问题和解决问题的思维方式。既然是一种思想,那么它就不是C#程序员或者Java程序员的专属。通常在评价一种语言是否支持面向对象时,首先要看就是这种语言中是否有类的概念。注意这里说的是\"类的概念\"(更多的是指意识形态上的类的概念),而不是\"类\
一个语言的特性是由编译系统与运行系统共同支持的,C++与C语言的区别,其实本质还是g++和gcc编译器的区别,要看编译器做了什么,比如this指针是g++编译器生成汇编才可以使用的
C/C++语言
CPU32Bit:x86汇编(Intel)64Bit:x64 64位汇编(AMD)差别:寄存器的数量、写法上的差异(调用约定)指令集类型CISC(Complex Instructioin Set Computer,复杂指令集)是一种微处理器指令集架构,每个指令可执行若干低端操作,诸如从存储器读取、存储、和计算操作,全部集于单一指令之中。与之相对的是精简指令集。复杂指令集的特点是指令数目多而复杂,每条指令字长并不相等,电脑必须加以判断,并为此付出了性能的代价。主要应用于PC端RISC(Reduced Instruction Set Computer,精简指令集)是计算机中央处理器的一种设计模式。这种设计思路可以想象成一家模块化的组装工厂,对指令数目和寻址方式都做了精简,使其实现更容易,指令并行执行程度更好,编译器的效率更高。主要应用于手持设备汇编风格Netwide Assembler(简称NASM)是一款集于英特尔x86架构的汇编与反汇编工具,它可以用来编写16位、32位、64位的程序,NASM被认为是Linux平台上最受欢迎的汇编工具之一。它采用的是复杂指令集CISCMicrosoft Macro Assembler(简称MASM),它是微软位x868微处理器家族,所写的一套宏汇编器。它最初是用来发展MS-DOS上面执行的软件,同时,它也是该系统最流行的汇编器。采用的的是复杂指令集CISCARM(Advanced RISC Machine)汇编是一种精简指令集(RISC)处理器架构家族,其广泛地使用在许多嵌入式系统设计。由于节能的特点,ARM处理器非常适用于移动通信李玲玉,符合其主要设计目标为低成本、高性能、低耗电的特性。另一方面,超级计算机消耗大量电能,ARM同样被视作更高效的选择,其在其他领域上也有很多作为。M1(集于ARM架构)和Arm芯片虽然汇编庚哥不同,但是底层的硬编码是一样的
操作系统基本认识
CPU有多少寄存器?(一个核一组寄存器)x86架构中有8个通用寄存器(GPR)、6段寄存器、1个标志寄存器和指令指针。64位的x86有附加的计算器8个GPR是:1.累加器寄存器(AX)。用在算术运算.函数返回值存储2.基址寄存器(BX):作为一个指向数据的指针(在分段模式下,位于段寄存器DS)3.计数器寄存器(CX):用于移位/循环指令和循环。循环次数、this指针4.数据寄存器(DX):用在算术运算和IO操作5.堆栈指针寄存器(SP):用于指向堆栈的顶部。形式上的栈帧6.栈基址指针寄存器(BP):用于指向堆栈的底部。形式上的栈帧7.源变址寄存器(SI):在流操作中用作源的一个指针。数据拷贝用8.目标索引寄存器(DI):用作在流操作中指向目标的指针。 数据拷贝用7.程序指针寄存器(IP):程序计数器(OS层面)、常量池索引(r13寄存器).这个寄存器只有操作系统能改8.其他寄存器(r8、r9、r10、r11、r12、r13):函数调用有一种快速调用,前6个参数通过寄存器来传递,但是超过6个参数,则通过堆栈,其中r8存储第5个参数、第6个参数,剩下的r10、r11、r12、r13寄存器则没有限制用OD可以看到当前系统中的寄存器类型,在64bit系统中,寄存器是以r开头,32bit中,寄存器是以e开头,比如rax和eax的寄存器类型是相同的,它们都作为函数返回值存储来使用通用指令pushad:将所有32位通用寄存器压入堆栈pusha:将所有的16位通用寄存器压入堆栈pushfd:然后将32位标志寄存器EFLAGS压入堆栈,包含push ebx/pushesi/ push edipushf:将所有16位标志寄存器EFLAGS压入堆栈popad:将所有的32位通用寄存器取出堆栈popa:将所有的16位通用寄存器取出堆栈popfd:将32位标志寄存器EFLAGS取出堆栈 包含pop ebx/pop esi/ pop edipopf:将16位标志寄存器EFLAGS取出堆栈在JVM中的程序计数器用的是r13寄存器,OS用的EIP寄存器
线程切换线程->修改的数据的线程->怎么知道线程切换-> TSS寄存器。会映射到kuser_shared_data
JCC指令任何语言的底层,循环结构及条件判断,都是集于eflags寄存器+JCC指令实现的。
1.深入理解寄存器
```cvoid add(int) {int a = 10;int b = 20;int sum = a + b;}int main(){add(10);return 0;}```
一个函数的汇编结构
生成的堆栈图
堆栈图```cint add() {return 10;}int main(){int a = add();return 0;}```
如何调试JVM的执行流?在Clion里面用gdb调试器命令行进行调试1.找到2.b *0x0007fffed01eb00(这是一个entry point的起始地址)3.再输入c,就可以跳到hotspot的汇编指令处
2.深入理解函数、堆栈图
函数调用约定类型__cdecl__stdcall__fastcall调用约定传参的方式1.纯粹用栈2.纯粹用寄存器3.栈+寄存器堆平衡1.内平栈2.外平栈编译器生成的代码的两个原则:1.函数中用到的寄存器,进入方法之前,退出以后,寄存器的状态应该是一样的2.堆栈需要平衡(内平栈与外平栈)Windows下全部支持Ubuntu16仅支持快速调用
裸函数的反汇编代码
lock_object中的用法
3.函数调用约定、内联汇编、裸函数
gcc、g++、gdb、lldb、kgdbC语言编译器:gccC++编译器: g++应用层调试器:gdb、lldb内核层:Windows: windbgLinux: kgdb + qemu
定义的字符串数组不能被修改,否则会报段异常
从应用层理解字符串原生语言是没有祖父穿这个数据结构的,所以如果要想使用字符串,那么就必须要自己封装。比如Java是用char[]数组或者byte[]数组来实现的,C++中也是用char[]数组实现的,Redis中是用SDS封装的那么,字符串太多,就会引入字符串常量池,引入常量池,就会引起hashtable、key-value和内存的控制,以及内存淘汰算法,以及常量池去重字符串存在形式1.字符数组2.常量池无法修改3.字符串类型指针OS怎么知道字符串的结束呢?需要\\0结尾比如说char *str = \"cover\";str[0]=\"C\
4.C语言基础
指针的两种理解1.特殊数据类型 **特性于操作内存地址是完全匹配的指针类型,其实它也是一个容器2.内存地址很重要的一句话:你对内存的理解,决定了你用指针的熟练度
指针的用法```cchar* c1 = (char*)10;int* i1 = (int*)10;char* c2 = c1 + 1;int* i2 = i1 + 1;printf(\
指针的用法2指针的宽度:32bit 4B64bit 8B
指针与运算只支持加减大多数情况,指针与整型进行运算,指针与指针之间运算也存在,但是极少
指针的使用案例```cint i = 0x11223344;int* p = &i;// 想通过p获取33,代码怎么写?// 写法一printf(\"%x\\
5.玩转指针
C语言中最重要的就是指针和结构体
我们想构建执行流1.申请内存(可读可写不可执行的)改进:不能用普通的申请内存的函数malloc、callloc得用mmap2.执行流的字节码指令哪里来?new字节码指令对应的执行流代码怎么来的获取执行流指令的两种方式1.写好C代码,然后把编译后生成的代码copy2.编译原理 硬编码编织3.第二部拿到的执行写入第一步申请的内存中4.如何调用需要有函数名指向它汇编:jmp+返回地址压栈、callC语言:函数指针(指针函数 返回指针的函数)1.声明一个函数指针的类型2.定义一个函数指针
如何研究JVM的执行流模板解释器,编译优化的彻底性在哪里,节省栈帧的开辟函数名意味着什么?指向这个函数的代码段call调用一个代码片段需要有一个前提条件,那段内存得有一个执行权限JVM中的执行流代码存在JVM进程的堆当中。OS里有很多进程,每个进程有1.代码段2.数据区、字符串常量3.栈4.堆(JVM的运行时数据区就在其中)但是这个运行时数据区是可读可写不可知性的,所以需要修改页属性,赋予其可执行权限,所以说需要用mmap申请,它既可以申请内存,又可以申请权限
HotSpot可以代表软件工业界(C/C++/汇编的最高水平)call_stub和entry_point是Java与JVM的一道桥。JVM中生成的执行流生成栈帧的汇编指令如图所示generate_fixed_frame()
6.C语言如何开发
C语言面向对象编程前提时需要g++编译器的支持,原生gcc是不支持1.封装 struct封装2.构造函数 struct里面写构造函数3.this指针4.析构函数写法5.继承,支持多重继承,公有继承、私有继承、保护继承,一般也是用单继承,公有继承6.多态
HotSpot中的类初始化的加锁逻辑
ObjectLocker中释放锁的逻辑
4.析构函数写法~A() {}特点1.没有返回值2.没有参数3.调用:不用手动调用,delete对象的时候、对象的作用域过了以后,系统自动调用作用1.回收内存2.妙用(HotSpot)我们知道一个类只能被初始化一次,会通过加锁的方式来保证线程安全,那么在HotSpot源码中,这个锁是怎么加的呢?答案就是通过代码块将锁的作用域限制住,当代码块被执行完的时候会调用锁的析构函数,在析构函数里面进行锁的释放
调用虚函数。eax寄存器存储了函数返回值
虚函数内部的结构,只要有1个虚函数,就会生成虚表,无论虚函数的数量多少,虚表也是占用4B
6.多态静态绑定 call 0x7fff1421动态绑定、晚绑定、间接call call[0x7ff63636]动态绑定在调用时,调用的是虚表的地址。虚表是多态得以实现的基础。虚表是一个数组结构。虚表1.虚表的位置:对象的头部2.虚函数对结构体大小的影响不管有多少虚函数,只有一个虚表32bit=4B64bit=8B3.OS如何调用虚表中的方法4.模拟调用虚表
结构体的两种分配方式struct A {}栈上分配A a;a.test();堆上分配A a = new A;a->test();
公有继承、私有继承、保护继承的区别在面向对象编程中,继承是一种非常重要的特性,它允许我们创建新的类(子类)来继承现有类(父类)的属性和方法。通过继承,子类可以获得父类的所有非私有属性和方法,并可以添加或覆盖它们以实现特定的功能。然而,继承并非总是将所有父类的成员直接暴露给子类,而是根据继承方式的不同,子类的父类成员的访问权限也会有所不同。1.公有继承(public inheritance)(Java中的继承是公有继承)公有继承是最常见的继承方式。在公有继承中,父类的公有成员和保护成员在子类中仍然保持其原有的访问权限,即公有成员和保护成员在子类中仍然是公有的,可以被子类的实例直接访问。而父类的私有成员在子类中是不可见的,即子类无法直接访问父类的私有成员。公有继承允许子类访问并扩展父类的公有和保护成员,这使得子类能够灵活地继承父类的功能并实现新的功能2.私有继承(private inheritance)私有继承是一种相对不常见的继承方式。在私有继承中,父类的公有成员和保护成员在子类中都被视为私有成员,即子类无法直接访问父类的公有成员和保护成员,这种继承方式将父类的所有成员都隐藏起来,只允许子类通过继承来的方法进行间接访问。私有继承的一个主要应用场景是实现接口或抽象类,即子类只需要实现父类的方法,而不需要直接访问父类的成员3.保护继承(protected inheritance)保护继承是另一种相对不常见的继承方式。在保护继承中,父类的公有成员和和保护成员在子类中都被视为保护成员,即子类无法直接访问父类的公有成员和保护成员,只能通过派生类(子类的子类)或友元函数进行访问。这种继承方式在一定程度上介于公有继承和私有继承之间,它既不像公有继承那样暴露父类的所有成员,也不像私有继承那样完全隐藏父类的成员。保护继承通常用于实现一些特定的设计模式和架构,例如模板方法模式等总的来说,公有继承、私有继承和保护继承在面向对象编程中具有不同的应用场景和特点。公有继承允许子类灵活地访问和灵活地访问和扩展父类地公有和保护成员;私有继承将父类地所有成员都隐藏起来,只允许子类通过继承来的方法进行间接访问;保护继承则介于两者之间,允许子类通过派生类或友元函数访问父类的公有和保护成员。在实际编程中,我们应该根据具体的需求和场景来选择合适的继承方式来实现代码复用和扩展功能此外,需要注意的是,不同的编程语言可能对继承方式的支持程度和语法细节有所不同。因此,在实际编程中,我们应该参考具体的编程语言文档和规范来了解和使用继承方式。同时,我们应该遵循良好的面向对象设计原则和实践,如单一职责原则、开放封闭原则、里氏替换原则等,来确保代码的可读性、可维护性和可扩展性。最后,值得一提的是,虽然继承是一种强大的代码复用和扩展机制,但它并非总是最佳的选择,在某些情况下,组合(Composition)可能是一个更好的选择。组合允许我们将一个类的对象当作另一个类的成员来使用,从而实现代码复用和扩展功能。与继承相比,组合通常更加灵活和易于理解。因此在实际编程中,我们应该根据具体需求和场景来权衡继承和组合的使用
C++中的多态C++多态多态按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。C++多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。导致错误输出的原因是,调用函数area()被编译器设置为基类中的版本。这就是所谓的静态多态,或静态链接-函数调用在程序执行前就准备好了,有时候这也被称为早期绑定,因为area()函数在程序编译器就已经设置好了。但现在,让我们对程序稍作修改,在Shape类中,area()的声明前放置关键字virtual,他就会产生以下结果:Rectangle class area :Triangle class area :此时,编译器看的是指针的内容,而不是它的类型。因此,由于tri和rec类的对象地址存储在*shape中,所以会调用各自的area()函数。正如我们所看到的,每个子类都有一个函数area()的独立实现。这就是多态的一般使用方式。有了多态,我们就可以有多个不同的类,都带有同一个名称但具有不同实现的函数,函数的参数甚至可以是相同的。
Java中的多态重写(OveriWrite)重写是子类对父类的允许访问的方法的实现过程进行重新编写,返回值和形参都不能改变,即外壳不变,核心重写。重写的好处在于子类可以根据需要,定义特定于自己的行为。也就是说子类能够根据需要实现父类的方法。重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。例如,父类的一个方法声明了一个检查异常IOException,但是在重写这个方法的时候不能抛出Exception异常,因为Exception是IOException的父类,只能抛出IOException的子类异常。在面向对象原则里,重写意味着可以重写任何现有方法方法的重写规则:1.参数列表必须完全与被重写方法相同2.返回类型必须完全与被重写方法的返回类型相同3.访问权限不能比父类中被重写方法的权限更低。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected.4.父类的成员方法只能被它的子类重写5.声明为final的方法不能被重写6.声明为static的方法不能被重写,但是能够被再次声明7.子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为private和final的方法8.子类和父类不在同一个包中,那么子类只能够重写父类的声明为public和protected的非final方法9.重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以10.构造方法不能被重写11.如果不能继承一个方法,则不能重写方法方法的重写发生在运行时,因为在编译时,编译器是无法知道我们到底是调用子类的方法,相反的,只有在实际运行的时候,我们才知道应该调用哪个方法。这个也是Java运行时多态的体现。重载(Overload)对于类的方法(包括从父类中继承的方法)。如果有两个方法的方法名相同,但参数不一样,则一个方法是另一个方法的重载方法。重载方法必须满足以下条件:1.方法名相同2.方法的参数类型、个数、顺序至少有一项不同3.方法的返回值类型可以不同4.方法的修饰符可以不同。在一个类中不允许定义两个方法名相同,并且参数签名也完全相同的方法。因为加入存在这样的方法,Java虚拟机在运行时就无法决定到底执行哪个方法,参数签名是指参数的类型、个数和顺序。注意:重载方法跟其返回类型没有关系方法的重载发生在编译期。在编译过程中,编译器必须根据参数类型以及长度来确定到底是调用的哪个方法,这也是Java编译时多态的体现。
7.C++
深入理解汇编/C/C++
0 条评论
回复 删除
下一页