首页 > 编程语言 >C++逆向分析——继承与封装

C++逆向分析——继承与封装

时间:2023-04-09 10:55:43浏览次数:37  
标签:逆向 封装 Level int Age C++ Sex Person 父类

面向对象程序设计之继承与封装

之前已经学习过继承和封装了,但是要在实际开发中使用,光学语法和原理是不够的,在设计层面我们需要做一些优化。

如下代码是继承的例子:

#include <stdio.h>   class Person { public: int Age; int Sex;   void Work() { printf("Person:Work()"); }   };   class Teacher:public Person { public: int Level; };   void main() { Teacher t; t.Age = 10; t.Sex = 1; t.Level = 2;   t.Work();   return; }

当t.Age=-1,这在代码层面(语法)是合法的,但是不合理,因为人的年龄不可能是负数;所以从设计层面以上代码就不正确、不合理。

所以,我们可以将不想被外界访问的成员隐藏起来,也就是使用private关键词:

#include <stdio.h>   class Person { private: int Age; int Sex; public: void Work() { printf("Person:Work()"); }   };   class Teacher:public Person { private: int Level; };

但是这样,如上代码就会出现问题,因为我们没法直接访问到成员,因此从设计层面出发设计这个,我们可以提供按钮或者说一个函数用来控制这些值:

#include <stdio.h>   class Person { private: int Age; int Sex; public: void Work() { printf("Person:Work()"); } void SetAge(int Age) { this->Age = Age; } void SetSex(int Sex) { this->Sex = Sex; }   };   class Teacher:public Person { private: int Level; public: void SetLevel(int Level) { this->Level = Level; } };

而后我们可以通过函数去设置这些值,那有人就会问了,你这样不还是可以输入-1吗?是的,是可以输入,单同样,我们可以在成员函数内做条件判断来控制输入的内容:

void SetAge(int Age) { if (Age > 0) { this->Age = Age; } else { this->Age = 0; } }

用成员函数控制就不会存在别人想要调用这个类的时候存在合法不合理的情况了,其根本的目的就是可控。(数据隐藏)

除了成员数据(变量)以外,还有一些提供给自己用的成员函数也要隐藏。

但这样随之而来的问题也就产生了,一般情况下,我们是想要在创建对象的时候就赋值了,也就是说我们使用构造函数去赋值,那这时候如果父类存在构造函数,使用子类创建对象的时候,子类默认会调用父类无参的构造函数,也就是说父类如果存在有参的构造函数被继承,就必须要有无参的构造函数。

所以一个好的习惯:当你写一个类的时候,就应该写一个无参的构造函数

class Person { private: int Age; int Sex; public: Person() { } Person(int Age, int Sex) { this->Age = Age; this->Sex = Sex; } void Work() { printf("Person:Work()"); }   };   class Teacher:public Person { private: int Level; public: Teacher() { } Teacher(int Level) { this->Level = Level; } };

如上代码,调用Teacher创建对象,我们想通过构造函数赋值Age和Sex该怎么办?第一时间想到的时候使用this调用,但是这里是继承父类的,肯定不行。

C++也提供了这种情况下的语法:

Teacher(int Age, int Sex, int Level):Person(Age, Sex) { this->Level = Level; }

在子类有参构造函数中加入参数列表,而后在括号后门加上冒号跟上父类有参构造函数,传入变量即可。

有些人就疑问了,为什么这种写法不可以呢?

Teacher(int Age, int Sex, int Level) { Person(Age, Sex); this->Level = Level; }

这只有利用反汇编代码来解释了:

images/download/attachments/12714553/image2021-4-10_0-20-53.png

如上反汇编代码,可以很清楚的看见当我们不使用那种方法还是会调用一遍父类无参的构造函数,接着手动添加的构造函数,编译器会把堆栈中临时分的对象赋值,但是当我们这段构造函数执行完成之后就没了,所以没有任何意义。

面向对象程序设计之多态

C++是一门面向对象的编程语言,所有的面向对象语言都有一个特征:封装、继承、多态;之前已经了解过封装、继承了,这里来了解一下多态。

所有的面向对象的编程语言在设计的时候都是为了解决一个问题,那就是避免重复造轮子,也就是避免写2遍重复的代码,我们也可以称之为代码复用,其体现方式有2种:1.继承;2.共用相同的函数。

现在我们有一个需求,需要打印对象的成员变量,如下代码:

#include <stdio.h>   class Person { private: int Age; int Sex; public: Person() { } Person(int Age, int Sex) { this->Age = Age; this->Sex = Sex; } void Print() { printf("%d \n", this->Sex); }   };   class Teacher:public Person { private: int Level; public: Teacher() { } Teacher(int Age, int Sex, int Level):Person(Age, Sex) { this->Level = Level; } };   void PrintPerson(Person& p) { p.Print(); }

我们创建了一个PrintPerson函数来调用Person的Print函数,但是在这里如果我们想要打印Teacher的成员呢?那就需要创建2个打印函数了,也就是违背了面向对象的初衷,重复造轮子了。

在C++中我们可以使用父类的指针来指向子类的对象:

void main() { Person p(1,3); Teacher t(1,2,3);   Person* px = &t;   return; }

如下图我们可以很清晰的看见内存的结构,当我们形容子类B内存结构的时候,一定是有三个成员的,而不是一个成员z,当我们创建A*指针的时候指向的是子类对象的首地址,通过这个指针可以访问x、y,刚好子类对象B的开始位置是父类类型对象的第一个成员,所以我们可以使用父类类型的指针指向子类类型对象;但是反之(子类类型的指针指向父类类型的对象)我们却不可以,这是因为使用父类类型的指针指向子类类型对象有一个弊端,那就是没法访问子类类型的z,反过来的话,父类类型对象的成员只有x、y没有z,所以我们通过子类类型指针访问的时候是可以访问到三个成员的:x、y、z,但实际上父类对象是没有z的,那么在访问的过程中就会存在问题。

images/download/attachments/12714553/image2021-4-11_23-53-55.png

所以我们可以只保留PrintPerson函数,而不再去重复造轮子:

images/download/attachments/12714553/image2021-4-12_0-23-19.png

如上代码仅仅是为了解决这种问题而举例的,所以代码严谨性可以忽略。

但是这样的弊端,就很清楚了,就是我们通过父类类型的指针指向子类类型的对象,是无法访问到子类类型自己本身的成员,只能访问到继承父类类型的成员。

所以这个还是无法满足我们的实际需求,那我们想不改变原有PrintPerson函数的情况下,只有在子类中重写Print函数才能到达需求(函数重写):

class Teacher:public Person { private: int Level; public: Teacher() { } Teacher(int Age, int Sex, int Level):Person(Age, Sex) { this->Level = Level; } void Print() { Person::Print(); printf("%d \n", this->Level); } };

Person::Print();是先调用父类的函数,但是在这里就可以打印了吗?实则不然:

images/download/attachments/12714553/image2021-4-12_0-22-38.png

我们可以看下反汇编代码,查看函数PrintPerson:

images/download/attachments/12714553/image2021-4-12_0-14-35.png

首先这里传递的是父类的引用类型,而后去调用的Print函数也是Person父类的,所以这样还是没法满足我们的需求。

我们可以使用一个关键词去解决这个问题,那就是在父类的Print函数类型前面加上virtual,则表示这是一个虚函数(其作用:当你PrintPerson函数传入的对象是子类就调用子类的,是父类就调用父类的):

images/download/attachments/12714553/image2021-4-12_0-24-8.png

这时候我们就可以引出多态的概念:多态就是可以让父类的指针有多种形态,C++中是通过虚函数实现的多态性

多种形态的表现,我们就已经在如上例子中说么了。

没有方法体的函数我们称之为纯虚函数,也就是说如下例子:

virtual int area() = 0;

纯虚函数

  1. 虚函数目的是提供一个统一的接口,被继承的子类重载,以多态的形式被调用;

  2. 如果父类中的虚函数可以任何意义,那么可以定义成纯虚函数;

  3. 含有纯虚函数的类被称之为抽象类,不能创建对象;

  4. 虚函数可以被直接调用,也可以被子类重写后以多态的形式调用,而纯虚函数必须在子类中实现该函数才可以使用。

标签:逆向,封装,Level,int,Age,C++,Sex,Person,父类
From: https://www.cnblogs.com/bonelee/p/17299991.html

相关文章

  • C++逆向分析——多态和虚表
    虚表上一章了解了多态,那么我们来了解一下多态在C++中是如何实现的。了解本质,那就通过反汇编代码去看就行了,首先我们看下非多态的情况下的反汇编代码:然后再来看下多态情况下的反汇编代码:很明显这里多态的情况下会根据edx间接调用,而非多态则会直接调用。那么我们来看下间接......
  • OS-Linux-Ubuntu22.04x64-Python-C++调用Python缺少Python.h
    OS-Linux-Ubuntu22.04x64-Python-C++调用Python缺少Python.h使用C或C++扩展Python扩展和嵌入Python解释器Python3.10.11Python/CAPI参考手册Python3.11.3Python/CAPI参考手册参考https://www.cnblogs.com/lidabo/p/17043302.htmlhttps://blog.csdn.net/z......
  • C++ Allocator
    C++Allocator该开始搞项目了,但是在搞项目之前,先搞一下C++的Allocator,因为项目中会用到。现在还不确定到底模仿哪个,jemalloc和tcmalloc,先看看吧。1.为什么需要Allocator其实和new/delete是等价的,但是可以屏蔽一些底层细节,因为不一定所有平台都提供统一的内存分配方式,所以需要......
  • 2023年第14届蓝桥杯大赛软件赛省赛 C/C++ 大学 B 组
    2023年第14届蓝桥杯大赛软件赛省赛C/C++大学B组试题A:日期统计(5)直接暴力,8个for+优化,2~5分钟跑完。答案:365点击查看代码#include<bits/stdc++.h>usingnamespacestd;typedeflonglongLL;constintN=1e6+10,INF=0x3f3f3f3f;intmon[]={0,31,28,......
  • C++函数重载和对象
    函数的默认参数intfun(inta,intb=10,intc=20){returna+b+c;}intmain(){cout<<fun(10)<<endl;//当参数有值时也可以输出正确,如果程序员加了返回值就用程序员加入的值。}形参的数据从左到右当中间有参数后面也需要定义一个函数和声明只能实现一个;占位参数voidfun(int*a,int......
  • 第十四届蓝桥杯大赛软件赛省赛C/C++大学生B组
    第十四届蓝桥杯大赛软件赛省赛C/C++大学生B组试题A:日期统计A题直接枚举即可,枚举日期,暴力匹配#include<iostream>#include<algorithm>#include<vector>usingnamespacestd;boolcheck(stringt){ if(t.substr(0,4)!="2023")returnfalse; stringmon=t.substr(4,2......
  • C++多核多线程同步实现
    使用MakefileC++11工程模拟dsp的多核同步逻辑,使用多线程模拟多核,多个线程通过C++11的条件变量实现同步。当某一线程执行到同步函数syn_func时,判断是否其他线程执行到此处了,若有其他线程没有执行到此处,本线程就应阻塞。当最后一个线程执行到同步函数时,通知所有线程解除阻塞,实现......
  • C++,OpenCV图像像素运算(6)
    参与算术运算图像的数据类型、通道数目、大小必须相同算术运算加法:addvoidadd(InputArraysrc1,InputArraysrc2,OutputArraydst,InputArraymask=noArray(),intdtype=-1);/******************************************************************** src1: 输入图1* ......
  • 遍历JsonObject 所有的key和value封装到两个list中
    可以使用main方法测试体验效果publicstaticvoidmain(String[]args){StringoriginalStr="{\"code\":200,\"msg\":\"success\",\"data\":{\"realName\":\"超级管理员\",\"defaultApp\":......
  • C++primer第五章
    5.1 简单语句表达式语句的作用是执行表达式并丢弃掉求值结果。最简单h的语句是空语句,空语句中只有一个单独的分号。复合语句是指用花括号括起来的语句和声明序列,复合语句也被称为块。一个块就是一个作用域。5.2 语句作用域定义在控制结构内的变量作......