C语言到C++的过渡

C++是由C演变过来的,两者必定是有联系的。这篇博客主要是过渡C,引出一些C++的基础概念,下面我会用C来模拟并借助汇编的方式来帮助大家更好的理解C++。

typedef struct Base
{
    int a;
    int b;
}Base;

int getMax(Base base)
{   
    return base.a >= base.b ? base.a : base.b;
}

int main(int argc, char* argv[])
{
    Base test; //局部变量
    test.a = 1;
    test.b = 3;
    int res = getMax(test);
    printf("%d\r\n",res);
    return 0;
}

首先,对于结构体作为参数传递OK不?运行结果这个可以的,那是这里是传递指针,还是传递结构体复制传递呢,我们观察其反汇编

22:       int res = getMax(test);
00401096 8B 45 FC             mov         eax,dword ptr [ebp-4] ;test.a
00401099 50                   push        eax
0040109A 8B 4D F8             mov         ecx,dword ptr [ebp-8] ;test.b
0040109D 51                   push        ecx
0040109E E8 62 FF FF FF       call        @ILT+0(getMax) (00401005)
004010A3 83 C4 08             add         esp,8 ;外平栈
004010A6 89 45 F4             mov         dword ptr [ebp-0Ch],eax

第一发现其使用了堆栈传参,相当于拷贝了一个副本进去,第二其使用了外平栈 。如果参数过多呢,这里的话就不贴反汇编了,它会先提升栈顶,栈顶指针当成副本的首地址,对其进行拷贝赋值。

所以在开发过程中,最好不用直接使用结构体直接进行参数的传递,而是使用结构体指针,使用修改成如下代码

int getMax(Base *base)
{   
    return base->a >= base->b ? base->a : base->b;
}

int main(int argc, char* argv[])
{
    Base test;
    test.a = 1;
    test.b = 3;
    int res = getMax(&test);
    printf("%d\r\n",res);
    return 0;
}

 观察其反汇编,会发现其会压入一个首地址进去,使用的是外平栈

22:       int res = getMax(&test);
00401096 8D 45 F8             lea         eax,[ebp-8] ;取test的首地址
00401099 50                   push        eax
0040109A E8 70 FF FF FF       call        @ILT+10(getMax) (0040100f)
0040109F 83 C4 04             add         esp,4 ;外平栈
004010A2 89 45 F4             mov         dword ptr [ebp-0Ch],eax

下面再改进代码,打印下该结构体的大小

结果为8个字节,没问题。下面我们升级下,其实对于一个函数来说,放到哪里都是无所谓的,哪怕是放到结构体里面

typedef struct Base
{
    int a;
    int b;

    int getMax(Base *base)
    {   
        return base->a >= base->b ? base->a : base->b;
    }

}Base;

可以编译通过,并且运行结果发现结构体的大小好像并没有发生变化,还是8个字节,那这个函数放结构体里面和外面真的一样么,下面我们调用下

    //int res = getMax(&test); //error C2065: 'getMax' : undeclared identifier
    int res = test.getMax(&test);

之前的调用不管用了,因为现在对于编译器来说,getMax函数是结构体的一部分(成员),但是它并不在这个结构体里面。

所以我们应该和调用a,b一样的方式调用它

26:       int res = test.getMax(&test);
00401046 8D 45 F8             lea         eax,[ebp-8] ;test首地址
00401049 50                   push        eax
0040104A 8D 4D F8             lea         ecx,[ebp-8] ;多传了一个参数
0040104D E8 C2 FF FF FF       call        @ILT+15(Base::getMax) (00401014)
00401052 89 45 F4             mov         dword ptr [ebp-0Ch],eax
; 内平栈

看上面的汇编代码,和之前的全局函数进行比较,会发现多一个参数放入ecx,这个参数和test的首地址是一样的,而且是内平栈。这个多传的参数干啥用呢,我们需要先修改成如下代码

typedef struct Base
{
    int a;
    int b;

    int getMax()
    {   
        if(a >= b)
            return a;
        else
            return b;
    }

}Base;



int main(int argc, char* argv[])
{
    Base test;
    test.a = 1;
    test.b = 3;
    int res = test.getMax();
    printf("%d\r\n",sizeof(Base));
    return 0;
}

观察其反汇编

;-----------------call-----------------------------

25:       int res = test.getMax();
00401046 8D 4D F8             lea         ecx,[ebp-8] ;多传人的参数
00401049 E8 CB FF FF FF       call        @ILT+20(Base::getMax) (00401019)
0040104E 89 45 F4             mov         dword ptr [ebp-0Ch],eax


;----------------getMax----------------------------

11:       int getMax()
12:       {
00401080 55                   push        ebp
00401081 8B EC                mov         ebp,esp
00401083 83 EC 44             sub         esp,44h
00401086 53                   push        ebx
00401087 56                   push        esi
00401088 57                   push        edi
00401089 51                   push        ecx
0040108A 8D 7D BC             lea         edi,[ebp-44h]
0040108D B9 11 00 00 00       mov         ecx,11h
00401092 B8 CC CC CC CC       mov         eax,0CCCCCCCCh
00401097 F3 AB                rep stos    dword ptr [edi]
00401099 59                   pop         ecx ;多传入的参数 test首地址
0040109A 89 4D FC             mov         dword ptr [ebp-4],ecx ;放入 ebp-4
13:           if(a >= b)
0040109D 8B 45 FC             mov         eax,dword ptr [ebp-4]
004010A0 8B 4D FC             mov         ecx,dword ptr [ebp-4]
004010A3 8B 10                mov         edx,dword ptr [eax]   ;成员 a
004010A5 3B 51 04             cmp         edx,dword ptr [ecx+4] ;成员 b
004010A8 7C 07                jl          Base::getMax+31h (004010b1) ;比较
14:               return a;
004010AA 8B 45 FC             mov         eax,dword ptr [ebp-4]
004010AD 8B 00                mov         eax,dword ptr [eax]
004010AF EB 06                jmp         Base::getMax+37h (004010b7)
15:           else
16:               return b;
004010B1 8B 4D FC             mov         ecx,dword ptr [ebp-4]
004010B4 8B 41 04             mov         eax,dword ptr [ecx+4]
17:       }
004010B7 5F                   pop         edi
004010B8 5E                   pop         esi
004010B9 5B                   pop         ebx
004010BA 8B E5                mov         esp,ebp
004010BC 5D                   pop         ebp
004010BD C3                   ret

看完其汇编,那么说明如果我们把函数放到结构体里面,编译器会帮我们传递一个参数进来,就是当前这个结构体的首地址。在函数内会根据其传入的首地址找到对应的成员数据再进行操作。

所以,我们为什么要把函数放入结构体里面呢?

对于整个结构体的大小来说没有变化,放里面的话,当前的编译器会替我们传递一个值,什么值呢,就是当前结构体的首地址传入到函数。

为什么要有C++呢?

C++可以做的任何事C都能做,之所以学习C++,因为只要你遵守C++的语法,编译器会帮我们添加很多很多的额外功能,所以学习C++语法是学习相对于C来说编译器帮我们做了什么。(当然除此之外还有面向对象的思想)

简单来说,自己写的代码是C,编译器帮我们加的代码就是C++

什么是封装?

把函数扔到结构体里面就是封装,因为这样使用结构体变量比较方便

什么是类?

带函数的结构体就是类,结构体类型 -> 类

什么是对象?

通过结构体类型创建的变量

什么是成员函数?

结构体里面的函数就是成员函数。在外面写的函数可以认为是全局的,成员函数编译器会记着,这个函数是属于哪个类,底层和全局函数没有任何区别,实际上成员函数并不真正的属于类,只是编译器认为属于类。

什么是this指针?

this指针就是当前这个结构体对象的地址,编译器会默认传进去的,也就是之前编译器放到ECX里面的值(参数个数确定时,不定 长参数会最后一个传参)。这个this指针不管你用不用,编译器都会帮你传递这个值,这样子才能保证成员函数在逻辑上封装(编译器限制),编译时独立(底层和普通函数无区别)

下面我们可以来模拟下

int main(int argc, char* argv[])
{
    /*Base test;
    test.a = 1;
    test.b = 3;
    res = test.getMax();*/

    //模拟上面代码
    char test[8] = {0}; //test
    test[0] = 1; //a
    test[4] = 3; //b

    int res = 0;

    //cannot convert from 'int (__thiscall Base::*)(void)' to 'int (__cdecl *)(void)'
    //说明结构体内的函数传参数约定为: __thiscall
    //int (*fun)() =  &Base::getMax;
    int (Base::*fun)() =  &Base::getMax; //ok 获取成员函数地址
    __asm
    {
        lea ecx,test ;模拟this传参
        call fun
        mov res,eax
    }

    printf("%d\r\n",res);
	return 0;
}

上面我们使用了test数组模拟了结构体的内存结构,然后取其首地址给ECX,当成this指针传参,最后运行结果也正确。

下面我们再来探究下this指针:

    int getMax()
    {   
        //cannot convert from 'struct Base *const ' to 'char *'
        //char *temp = this;  error
        if(a >= b)
            return a;
        else
            return b;
    }

将this指针强转char*编译报错,可以根据报错看出this指针是个 Base *const类型的指针, 这是什么类型的指针呢,我们可以反着读

struct Base *const this;
this is a const pointer to Base struct
this 是一个常量指针指向了Base结构体

那么说明该指针也是不能++和--的,因为是个常量。而且this指针的作用只是指向该结构体对象的首地址,无其他含义,没必要做运算。

Over,最后再记录一个知识点,空结构体占多少字节?一字节,为啥,因为结构体里面只有成员函数怎么办,调用函数得传对象首地址(this指针),所以一字节相当于占个位,方便获取地址。

最后声明下,该篇只是为了更好的帮助理解C++,所以把结构体当成类来学习,虽然其本质上是没啥区别的,主要注意下,纯C文件是不支持这些语法的哈

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值