C++ 深入了解 函数, 虚函数, 单继承,多继承,指针,引用。

              最近又开始写项目服务器部分了, 再次接触了C++ 有了一些更深入的体会。记录一下,以免忘记~  之前学习C++ 差不错都是靠死记, 记住C++的用法,C++的特性,然后去使用。没有从根本上理解,导致 几年不用C++,就已经完全忘记,然后又要花好长时间去记忆,使用。所以要真正做到学会C++,必须要从根本上了解,这样才不至于有会忘记, 而且使用起来会更的心应手。

1、明确了一些定义

    关于指针, 刚开始学习指针的时候,总是一些模糊的印象,想不清楚具体是什么, 就知道死记用法。仅仅知道指针能指向一个对象,好似一个标识而。

    最近深入了思考了下,并且通过研究编译后的文件,有了些具体的东西。我们知道所有程序最终会解释编译成机器码的形式运行的,而我们的计算机都是会变成二进制文件,就是形如010101001这样。关于CPU如何执行 这些二进制文件,这关系到一些硬件知识,我目前也没有去了解过,有机会找块单片机研究下,不过我们只用知道最终程序都是会变成一条条指令去执行。那些指令抽象简单的概括就是把某个内存里面的值与另一个内存里面的值做一些操作(如加、减等)然后写到一个内存。程序执行过程简单来说就是,把一堆二进制指令和一些数据装载到内存中(全部是一堆二进制值),然后CPU执行那些指令,修改内存(包括申请新内存、释放旧内存、内存中数据运算或者赋值)。关于编译运行过程可以参考另一篇文章 C++编译、运行过程详解

   函数调用, 在汇编中一个Call指令,专门针对调用一个函数,Call 接收一个地址,让CPU去执行那个地址的指令集。这就是一个最简单的函数调用过程。当然函数调用必定会涉及到参数的传递。每一个执行程序,操作系统都会分配一个默认的程序栈空间,这个栈本质上就是计算机的内存空间而已,只不过提供了一些栈特性的支持。当初我们学习C++的时候,都是背了一些定义,函数调用过程会先将当期地址入栈,然后参数按照函数的调用约到一次入栈然后在去执行操作,执行完后依次出栈,最后回到函数执行前入职的地址, 这就完成了一个函数调用过程。但是函数入栈和出栈具体是怎样一个过程,其实当时在我脑海里面是比较模糊的概念的,这些知识都是被动接受,所以 也没有 仔细的去想明白。其实栈的特性我们都知道是先入后出,我们的函数栈其实就是内存中的一块地址,然后是低地址增加的,所以栈的大小是有限制的(为什么是像低地址增加,这个得去看看操作系统相关的东西了),函数操作栈是通过2个寄存器来操作的,一个是栈底部地址(ebp寄存器),一个是栈顶地址(esp寄存器)。函数调用时候回将ebp寄存器指向当期函数栈的地址,然后,初始化此函数需要的栈空间大小,每个函数需要的栈空间大小是能明确确定的,在编译期间就能知道,例如 void Test() {int i = 0; A* pA = new A(); A a; delete pA; }的栈空间大小是临时变量 i 占4个字节,临时指针pA占 4个字节(win32平台),临时对象a 占 sizeof(A)的大小,A a 会直接在栈里面构造出来,所以要占sizeof(A)的大小,所以函数需要的栈的大小在编译器就能确定的。然后执行中所谓变量其实都是地址而已,i=0其实就是讲栈里面的epb+0位置的大小为4字节的内存赋值为0,A* pA = new A(); 这个构造一个对象过程稍微复杂一些,会调用到new方法申请一块sizeoof(A)大小的堆内存,堆内存不同于栈内存,堆内存理论上是无限大的(具体区别可以去度年或者谷姐上查询,都有很多文章有详细的解答),然后调用A的构造方法,将对内存进行初始化操作(其中包括初始化虚函数表地址),pA实际上就是一个地址,在栈内存中,这个地址是A这个对象在对内存的地址,所以从这里可以看出其实所谓指针, 变量名 都是高级语言里面的概念,为了更好的编写程序而设计的,其本质 最终都是一个地址值而已。最终操作这个变量就是对这个地址操作。 例如对pA.m_nMenber = 0; 操作其实编译器最后会变成pA的地址加上这个变量所在的一个偏移地址offset的一个操作,即将pA地址+offset处一个4字节的内存赋值为0(0这个值是放在哪里的呢?我猜应该常量区吧,没有验证过,我猜应该是跟常量数据一样,最终执行过程就是把0所在的地址的4字节内容赋值到pA地址+offset处)。函数执行完成后, 会做出栈处理,其实就是将esp寄存器值指向ebp寄存器所以执行完后这段函数栈内存都会被释放掉,然后回到调用函数之前我们入栈的地址处。具体描述下函数调用过程就是,代码编译后运行,会加载到内存中,操作系统会分配一个栈给函数执行,每调用一个函数我们会先将当期函数执行后的下一条指令执行地址入栈(入栈操作其实就是将这个值保持到栈中,栈地址在增加,例如将一个int i入栈会将这个i所在地址复制到当期栈指针esp处(此地址在win32上占4字节)然后栈指针会加上4,即表示这个将变量i入栈了, 出栈就是将esp执行地址-4),在这个栈上初始化这段函数要用到的栈大小,然后依次执行此函数的指令,做一些变量复制、构造、函数调用等操作,然后依次将入栈的值出栈,回到调用之前入栈的那个执行地址继续执行。

当然 函数执行过程中还会有很多细节,如涉及到虚函数类的构造,虚函数的调用等,这些在使用上看上去是不同,当本质还是一样,只是这其中编译器会帮我们做一些操作来实现虚函数的特性。如在虚函数析构的时候编译出来的指令会调用父类的析构,等等操作。



我去发现要写清楚 真的好难, 每一个点都有太多东西要讲,想用一篇文章来描述 真的要写蛮长,写了这么长,我都还没开始讲到构造函数、虚函数、继承那些东西, 累了 休息先~





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值