滴水三期:day42.1-面向对象编程及this指针说明

文章介绍了C++中通过结构体模拟面向对象编程的概念,包括结构体与类的关联,成员方法的参数传递,特别是this指针的使用和作用。通过实例展示了成员方法如何在结构体中定义和调用,并解释了编译器在背后如何处理成员方法的调用,包括参数传递和调用约定。此外,还讨论了空结构体的大小以及空指针调用成员方法的情况,强调了this指针在函数执行中的重要性。
摘要由CSDN通过智能技术生成

一、面向对象编程

笔者由于学习过JAVA,所以对面向对象编程已经有一些自己的理解,下面不会讲解太详细

1.结构体<=>类

  • 先复习一下结构体作为参数传递:结构体作为参数传给函数,本质上是将结构体中所有成员复制一份传到函数的栈中。这样会比较浪费空间和时间

  • 所以我们会将结构体指针作为参数传递给函数:

    #include "stdafx.h"
    struct Base{
        int x;
        int y;
    };
    int Max(Base* basep){  //结构体指针传参
        return basep->x + basep->y;
    }
    int main(int argc,char* argv[]){
        Base base;
        base.x = 1;
        base.y = 2;
      	printf("%d\n",Max(&base));
        printf("%d",sizeof(base));  //打印一下结构体的大小
        return 0;
    }
    
  • 这里我们查看一下现在结构体的大小:8字节。现在如果把Max函数放到Base结构体中,再看看是否报错,并且查看此时结构体的大小:

    #include "stdafx.h"
    struct Base{
        int x;
        int y;
    	int Max(Base* basep){   //把Max函数放到Base结构体中
    		return basep->x + basep->y;
    	}
    };
    int main(int argc,char* argv[]){
        Base base;
        base.x = 1;
        base.y = 2;
      	printf("%d\n",base.Max(&base));  //这里调用函数时就需要用“结构体变量.”的方式调用
        printf("%d",sizeof(base));  //打印一下结构体的大小
        return 0;
    }
    
    • 函数作用范围受限:会发现完全可以把函数定义到结构体当中,但是在调用这个函数时需要把函数当成结构体中成员来调用:即base.Max()的方式来调用!
    • 结构体中函数不影响结构体大小:且发现结构体大小还是8字节!即Max函数当做结构体成员使用,但是却不包含在结构体当中
  • 综上:可以发现和面向对象的编程语言很像!结构体就是结构体中的变量就是成员变量结构体中的函数就是成员方法,使用Base base可以创建类的对象base,使用对象可以调用类中的变量和方法等,这些就是封装的思想

2.成员方法的参数传递

  • 见上述例子中:base.Max(&base),调用Base类中的方法,查看一下反汇编:发现虽然Max方法只有一个参数,但是却传入了两个参数!我们来分析一下:①lea eax,[ebp-8]push eax,这两个语句就是把base结构体变量的首地址使用栈的方式传递给了Max函数;②lea ecx,[ebp-8],这条语句就是把base结构体变量的首地址以寄存器传参的方式传递给了函数(因为进入进入函数中发现ecx中的值最终是被存储到了函数的栈中ebp-4的位置)

    175249 180031

  • 思考:为什么明明只应该有&base这一个参数,但是编译器又自动传递了一个结构体变量首地址给函数?所以我们大胆尝试一下:如果在结构体中定义一个无参数的成员函数,编译器会怎么做呢?

    #include "stdafx.h"
    struct Base{
        int x;
        int y;
    	int Max(){
    		return x + y;
    	}
    };
    int main(int argc,char* argv[]){
        Base base;
        base.x = 1;
        base.y = 2;
      	printf("%d\n",base.Max());  //3
        return 0;
    }
    

    使用反汇编再查看一下:发现即使类中的方法没有设置参数,编译器还是会自动把所属结构体变量的首地址作为参数传递给函数;而且即使函数中没有指明x,y是哪里来的,但是编译器会根据传入的结构体变量首地址去找结构体当中的x和y

    180945 181700

  • 综上:编译器调用类中的成员方法时,会自动传递一个参数,且这个参数就是此结构体变量的首地址,我们把这个指针类型变量就叫做==this指针==

  • 所以和C语言相比如果按照C++的语法格式和思想方法,定义类、成员变量、成员方法等,编译器会帮我们添加很多额外的功能,节省很多时间,代码编写也会便利很多。我们需要学的就是使用C++时,编译器帮我们做了哪些事情,使开发效率更高更便捷

3.this指针的使用

  • 既然this指针指向的是当前结构体变量的首地址,那么我们就可以按照最开始&base的使用方式使用this指针,比如:

    #include "stdafx.h"
    struct Base{
        int x;
        int y;
    	int Add(){
    		return this->x + this->y;  //this->
    	}
    };
    int main(int argc,char* argv[]){
        Base base;
        base.x = 1;
        base.y = 2;
      	printf("%d\n",base.Add());
        return 0;
    }
    

    使用结构体指针的方式使用类中的成员:this->x,但是这样做好像有点多余,那么this指针什么时候用呢?

  • 当成员方法的参数和所在类中的成员变量同名时,使用this来区分这个变量是成员变量

    #include "stdafx.h"
    struct Base{
        int x;
        int y;
    	void Init(int x,int y){  //定义初始化成员变量的方法
    		this->x = x;
            this->y = y;
    	}
    };
    int main(int argc,char* argv[]){
        Base base;
      	base.Init(1,2);  //直接使用Init方法对成员变量赋值
        return 0;
    }
    

    this->x = x就区分了左边的x是成员变量,右边是传进来的参数,即将传进来的参数x的值赋给成员变量x

  • 还可以使用this指针,返回当前结构体变量的首地址

    #include "stdafx.h"
    struct Base{
        int x;
        int y;
    	int getAddress(){
    		return *((int*)this);  //返回当前结构体变量首地址
    	}
    };
    int main(int argc,char* argv[]){
        Base base;
      	base.getAddress();
        return 0;
    }
    

    因为this指针指向当前结构体变量的首地址,即==指向当前对象的首地址==,且this是一个结构体*类型的变量,所以要将当前对象的首地址以int类型返回,就需要先把Base*转成int*,再加一个*,把这个地址值取出来

  • 注意:前面C语言学过的各种指针类型变量都可以做++,–,+/-一个数,比较等操作,但是this指针不允许重新赋值

4.成员方法压栈顺序和堆栈平衡

  • 编写一个普通函数和类的成员方法,分别调用这两个函数比较区别:

    #include "stdafx.h"
    struct Base{
        int x;
        int y;
    	void Init(int x,int y){  //定义成员方法
    		this->x = x;
            this->y = y;
    	}
    };
    int method(int x,int y){  //定义函数
    	return x + y;
    }
    int main(int argc,char* argv[]){
        Base base;
      	base.Init(1,2);
    	method(1,2);
        return 0;
    }
    
  • 通过反汇编发现:

    • 一般函数采用栈传递参数,参数从右到左压栈,使用外平栈(即cdecl调用约定);

    • 成员方法参数采用栈传递参数,参数从右到左压栈,但是使用内平栈,且this指针参数最后一个传递,并采用寄存器的方式传递给函数(thiscall调用约定)

      image-20230422191952689

二、作业

1.设计一个类

  • 要求设计一个类,有两个Int类型的成员x,y,并在类内部定义4个函数分别实现对x,y的加法、减法、乘法与除法的功能

    #include "stdafx.h"
    struct Calculator{
        int x;
        int y;
    	void Init(int x,int y){
    		this->x = x;
    		this->y = y;
    	}
    	int add(){
    		return this->x + this->y;
    	}
    	int sub(){
    		return x - y;
    	}
    	int mul(){
    		return x * y;
    	}
    	int div(){
    		return this->x / this->y;
    	}
    };
    int main(int argc,char* argv[]){
        Calculator cal;
    	cal.Init(4,2);
    	printf("%d %d %d %d",cal.add(),cal.sub(),cal.mul(),cal.div());
        return 0;
    }
    

2.空结构体

  • 定义一个空结构体,查看其大小

    #include "stdafx.h"
    struct Nothing{	
    };
    int main(int argc,char* argv[]){
    	Nothing nothing;
    	Nothing nothing1;
    	Nothing nothing2;
    	int size = sizeof(nothing);
    	printf("%X %X %X",&nothing,&nothing1,&nothing2);
    	printf("%d",size);
        return 0;
    }
    
    • 会发现空结构体的大小为1字节。而且通过反汇编发现,这个1字节直接是编译器直接赋上去的!

      image-20230422195946089
    • 而且就算是空的类(结构体),也可以生成对象!说明编译器肯定会给一个空结构体对象至少给1字节空间,否则如果空结构体真是空的,编译器凭什么还会给结构体对象分配内存空间。

    • 通过反汇编可以发现:定义的三个类的对象,但是对象所在地址都不一样!第一个nothing对象首地址为ebp-4;第二个nothing1对象首地址为ebp -8;第三个nothing2对象首地址为ebp-0xC

      200128

3.空指针调用成员方法

  • 判断下面两个代码能否正常执行,并说明理由

  • 下面代码中:定义了一个空指针p,接着又用这个空指针p调用的成员函数C++中是允许空指针调用成员函数的,那么p->Fn_1();到底是什么意思呢?

    • 我们知道当程序编译完成后,Person类中的成员函数Fn_1()Fn_2()的地址就已经确定了(因为此类所有的对象都共用一个成员函数!可以理解为一个全局变量的感觉),所以**p->Fn_1(),就是相当于执行Person::Fn_1(this);,且把this指向空**,而此时Fn_1()中并没有使用到this!所以this空不空无所谓!Fn_1还是可以正常执行
    • Fn_2同理
    #include "stdafx.h"
    struct Person{  		
        void Fn_1(){	
    		printf("Person:Fn_1()\n");
    	}	
        void Fn_2(){	
    		printf("Person:Fn_2()%x\n");
    	}	
    };  				
    int main(int argc, char* argv[]){				
    	Person* p = NULL;
    	p->Fn_1();	
    	p->Fn_2();		
    	return 0;	
    }
    
  • 而下面的代码:Fn_1()中同样还是没有使用this指针,所以按照上述理由,还是可以正常执行;但是Fn_2()中,x = 10相当于this->x = 10,这里由于this指向空,但是又用空指针->x,而空指针的地址是0x00000000,->x的本质前面学过,就是从0x00000000开始向后取4字节的值,这个地址是非法访问的,所以会报错!(或者直接理解成不能使用空指针即可)

    #include "stdafx.h"
    struct Person{  		
    	int x;	
        void Fn_1(){	
    		printf("Person:Fn_1()\n");
    	}	
        void Fn_2(){	
    		x = 10;   //这里相当于this->x = 10;
    		printf("Person:Fn_2()%x\n");
    	}	
    }; 				
    int main(int argc, char* argv[]){		
    	Person* p = NULL;		
    	p->Fn_1();	
    	p->Fn_2();		
    	return 0;	
    }
    
  • 那我们把代码改一下:把person对象地址赋给结构体类型的p指针,再用p指针调用成员方法,此时p->Fn_2(),就相当于调用Fn_2()成员方法,并且将this指针指向person对象,此时就可以正常执行this.x = 10

    #include "stdafx.h"
    struct Person{  		
    	int x;	
        void Fn_1(){	
    		printf("Person:Fn_1()\n");
    	}	
        void Fn_2(){	
    		x = 10;
    		printf("Person:Fn_2()%x\n");
    	}	
    };  		 				
    int main(int argc, char* argv[]){		
    	Person* p = NULL;		
    	p->Fn_1();
    	
        Person person;
    	person.Fn_2();  //这是正常用对象调成员函数
    	
        p = &person; //把person对象地址赋给结构体类型的p指针
    	p->Fn_2(); //此时p->Fn_2(),就相当于调用Fn_2()成员方法,并且将this指针指向person对象
    	
        return 0;	
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值