C++ 多态 (详解)

多态的概念

通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。举个栗子:比如买票这个行为,当普通人买票时,是全价买票;学生买票时,是半价买票;军人买票时是优先买票。

总的来说:多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。比如Student继承了Person。Person对象买票全价,Student对象买票半价。

多态的定义及实现

多态的构成条件

在继承中要构成多态还有两个条件:
1. 必须通过基类的指针或者引用调用虚函数
2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

class person
{
public:virtual void BT(){cout << "买票,全价" << endl;}
};class student : public person
{
public:virtual void BT(){cout << "买票,半价" << endl;}
};void Fan(person& p)
{p.BT();
}int main()
{person ps;student st;Fan(ps);Fan(st);return 0;
}

注意:接收对象的指针或引用,传递是父类就调用父类的函数,传递是子类就调用子类的函数。

在重写父类虚函数的时候,子类的虚函数在不加 virtual 关键字时虽然也能构成重写(因为继承后父类的虚函数被继承下来依旧保持虚函数的属性),这种方法不规范,不建议使用

虚函数的重写和其两个例外

虚函数的重写:

虚函数的重写(覆盖):子类中有一个跟父类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。

刚才多态的构成条件已经实现了虚函数的重写,这里不做过多赘述

虚函数重写的两个例外:
1.协变(父类与子类虚函数返回值类型不同)

子类重写基类虚函数时,与父类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。

这里不仅仅时返回当前父类和子类的类型,还可以返回其他继承关系的类和类类型

2.析构函数的重写(父类与子类析构函数的名字不同)

如果父类的析构函数为虚函数,此时子类析构函数只要定义,无论是否加virtual关键字,都与父类的析构函数构成重写,虽然父类与子类析构函数名字不同。虽然函数名不相同,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor。

首先,看看不加 virtual 的情况

本义是想看让p1调用person的析构,p2先调用person的析构再调用student的析构,但是这里并没有调用student的析构,只有父类的析构,这样可能发生内存泄漏。

这是为什么呢?

因为这里构成了隐藏,~person()变为 this->destructor()    ~student()为this->destructor()编译器将他们两个的函数名都统一处理destructor,因此调用的时候只看自身的类型,是person就调用person,student就调用student的函数,根本构不成多态,与我们期望的并不一样。

现在加上virtual关键字

student 能正常析构了

虽然说子类可以不加 virtual 关键字,建议不要这样,最好父类和子类都加上

同时析构函数加virtual是在new场景下才需要,其他环境可以不用

C++11 final和override

C++11提供了override和final两个关键字,可以帮助用户检测是否重写。

final:修饰虚函数,表示该虚函数不能再被重写

final修饰类不能被继承

override: 检查子类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。

重载、覆盖(重写)、隐藏(重定义)的对比

(只有重写要求原型相同,原型相同就是指 函数名/参数/返回值都相同)

函数重载:在同一个作用域中,两个函数的函数名相同,参数个数,参数类型,参数顺序至少有一个不同,函数返回值的类型可以相同,也可以不相同。

重定义(也叫做隐藏)是指在继承体系中,子类重新定义父类中有相同名称的非虚函数(参数列表可以不同),此时子类的函数会屏蔽掉父类的那个同名函数。

重写(也叫做覆盖)是指在继承体系中子类定义了和父类函数名,函数参数,函数返回值完全相同的虚函数。此时构成多态,根据对象去调用对应的函数。

抽象类

概念:在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成纯虚函数。

多态的原理

虚函数表

// 这里常考一道笔试题:sizeof(Base)是多少?
class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}
private:int _b = 1;
};

通过观察测试我们发现b对象是8bytes,除了_b成员,还多一个__vfptr放在对象的前面(注意有些平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)。一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表,那么派生类中这个表放了些什么呢?我们接着往下分析

我们多添加几个虚函数看看

可以发现虚函数会放到虚函数表里,普通函数不会,并且表的内容是一个数组,是函数指针数组

多态的原理

测试代码

class Person {
public:virtual void BuyTicket() { cout << "买票-全价" << endl; }virtual void fun(){}
private:int a;
};
class Student : public Person {
public:virtual void BuyTicket() { cout << "买票-半价" << endl; }
private:int b;
};
void Func(Person* p)
{p->BuyTicket();
}
int main()
{Person p;Student s;Func(&p);Func(&s);return 0;
}

虚表指针里的内容

从图中我们可以看到,在内存2中输入 &p 可以找到p的地址,因为p的第一个内容是 _vfptr,因此p的地址也就是_vfptr的地址,通过_vfptr地址里就可以找到虚函数表里的内容,因此内存1中输入_vfptr的地址,便找到两个函数的地址。

同理拿到 s 的

为什么我要费劲心思去看内存呢?监视窗口不是可以看嘛,因为vs2022的监视窗口不一定准确,但内存一定准确。

第二个虚函数的地址一样,因为fun()没有被重写而被继承下来了,而BuyTicket()被子类重写覆盖掉了,这就是为什么重写也被称为覆盖。

引用和指针如何实现多态

可以分析为什么指针指向父类调用父类的函数,指向子类调用子类函数?

传递父类,通过vptr找到找到虚函数表的位置,再去找到虚函数的地址,有了虚函数的地址,便可以去call这个虚函数。

传递子类会进行切割

将子类的内容切割掉父类再去接受这个数据,一样会有vptr(子类的vptr),再去找到虚函数的地址,有了虚函数的地址,便可call这个虚函数,这样实现了多态。

为什么普通类实现不了多态

我们用普通类

给代码做点小改造,给Person加上构造

同时给p对象传10,他会调用构造函数将10赋给成员a。执行Func(p)时注意此时虚函数表的地址为0x0000007ff7dfd3ac18

当执行Func(s)此时a的直为0,此时虚函数表的地址为0x0000007ff7dfd3ac18,没错相同!

不难看出Func(s)传递时,切割出子类中父类的那一份成员会拷贝给父类,但是没有拷贝虚函数表指针。

为什么只拷贝成员,不拷贝虚函数表呢?C++祖师爷为何这么设计?

我们可以反向思考,假设 拷贝构造 和 赋值重载 会拷贝虚函数表指针那么如下,运行后输出的结果就为 买票,半价 了(因为不管指向的什么,只管你所存储的数据)这样就不能保证多态调用时,指向父类,父类调用的是父类的虚函数。因为还有可能经过一些操作,变成子类的虚函数

或许这问题不致命,那么析构呢,要清楚虚函数表还可能有析构函数,遇到如下情况该如何

Person* p = new Person;
Student s;
*p = s;
deletep;

这个时候Person父类的对象delete回去调用子类Student类的析构函数,这样会引发很多不可控的事情。

可能有些绕,只需要知道只有 引用和指针才能触发多态即可!

最后同类对象的虚表一样,如果子类没有重写父类的粗函数,那么他们的虚函数表指针不同,但里面的内容相同

虚函数表存放的位置

各个区地的地址可以通过代码获得,以此来判断虚函数表的存放位置

class Base
{
public:virtual void fun1(){cout << "base::fun1" << endl;}virtual void fun2(){cout << "base::fun2" << endl;}
private:int a;
};void fun()
{}int main()
{Base b1;Base b2;static int a = 0;int b = 0;int* p1 = new int;const char* p2 = "hello word";printf("静态区:%p\n", &a);printf("栈:%p\n", &b);printf("堆:%p\n", p1);printf("代码段:%p\n", p2);printf("虚表:%p\n", *((int*)&b1));printf("虚函数地址:%p\n", & Base::fun1);printf("普通函数:%p\n", fun);}

注意打印虚表这里,vs X86环境下的虚表的地址时存放在类对象的头四个字节上。因此通过强转来获取这是个头字节。

b1是类对象,取地址取出类对象的地址,强转为(int*)代表我们只取四个字节,再解引用,即可获取第一个元素的地址,也就是虚函数表指针的地址

从结果来看代码段和虚表的地址非常接近,存放在代码段的常量区

虚函数和普通函数地址最为接近,存放在代码段

动态绑定与静态绑定(了解)

1. 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,
比如:函数重载
2. 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体
行为,调用具体的函数,也称为动态多态。

单继承和多继承关系的虚函数表

单继承中的虚函数表

测试代码

class Base
{
public:virtual void func1(){cout << "Base::func1" << endl;}virtual void func2(){cout << "Base::func2" << endl;}private:int a;
};class Derive : public Base
{
public:virtual void func1(){cout << "Derive::func1" << endl;}virtual void func3(){cout << "Derive::func3" << endl;}virtual void func4(){cout << "Derive::func4" << endl;}private:int b;
};class X : Derive
{
public:virtual void func3(){cout << "X::func3" << endl;}
};int main()
{Base b;Derive d;X x;return 0;
}

监视窗口中我们发现看不见func3和func4。这里是编译器的监视窗口故意隐藏了这两个函数,也可以认为是他的一个小bug。

打开内存窗口输入_vfptr的地址,找到d中的四个虚函数地址

在vs环境下,虚函数表里的虚函数都是以0结尾符合我们之前观察到的

我们可以通过这一点来打印虚表。

下面我们typedef了虚函数表指针  typedef void(*VFTPTR)(); 可以通过这个函数指针数组来打印里面的虚函数,这个打印函数终止条件就是 !=0 ,传递的参数内容跟前面我们分析的差不多,只是多了一个强转,PrintVFPtr((VFTPTR*)*(int*)&b)  ; 因为后面的  *(int*)&b 虽然内容是地址,但是表现形式是一个整形,需要强为  (VFTPTR*) 。

再*((int*)&d) 就会取到vTableAddress指向的地址,即可得到虚函数的地址

class Base {
public:virtual void func1() { cout << "Base::func1" << endl; }virtual void func2() { cout << "Base::func2" << endl; }
private:int a;
};
class Derive :public Base {
public:virtual void func1() { cout << "Derive::func1" << endl; }virtual void func3() { cout << "Derive::func3" << endl; }virtual void func4() { cout << "Derive::func4" << endl; }
private:int b;
};
class X : Derive
{
public:virtual void func3() { cout << "X::func3" << endl; }
};typedef void(*VFTPTR)();void PrintVFPtr(VFTPTR* a)
{for (size_t i = 0; a[i] != nullptr; ++i){printf("a[%d]:%p\n", i, a[i]);VFTPTR f = a[i];f();}cout << endl;
}int main()
{Base b;Derive d;X x;PrintVFPtr((VFTPTR*)(*((int*)&b)));PrintVFPtr((VFTPTR*)(*((int*)&d)));PrintVFPtr((VFTPTR*)(*((int*)&x)));return 0;
}

我们运行以下代码便可打印出虚表里面的内容

多继承中的虚函数表

class Base1 {
public:virtual void func1() { cout << "Base1::func1" << endl; }virtual void func2() { cout << "Base1::func2" << endl; }
private:int b1;
};class Base2 {
public:virtual void func1() { cout << "Base2::func1" << endl; }virtual void func2() { cout << "Base2::func2" << endl; }
private:int b2;
};class Derive : public Base1, public Base2 {
public:virtual void func1() { cout << "Derive::func1" << endl; }virtual void func3() { cout << "Derive::func3" << endl; }
private:int d1;
};typedef void(*VFPTR) ();
void PrintVTable(VFPTR vTable[])
{// 依次取虚表中的虚函数指针打印并调用。调用就可以看出存的是哪个函数cout << " 虚表地址>" << vTable << endl;for (int i = 0; vTable[i] != nullptr; ++i){printf(" 第%d个虚函数地址 :0X%x,->", i, vTable[i]);VFPTR f = vTable[i];f();}cout << endl;
}
int main()
{printf("%p\n", &Derive::func1);Derive d;//PrintVTable((VFPTR*)(*(int*)&d));PrintVTable((VFPTR*)(*(int*)&d));    PrintVTable((VFPTR*)(*(int*)((char*)&d+sizeof(Base1))));
}

结论就是:func1是重写的函数,再子类的两个父类的虚表中储存的func1地址不同,但是通过一系列的call这个地址,这个地址的内容又是jump到另一个指令,最终都会跳到子类重写的func1上

PrintVTable((VFPTR*)(*(int*)&d)); 

因为对象中存虚表指针,虚表指针中存的是虚表(一个指针数组),则需要先解引用访问到这个对象的前四个字节内容(存的就是虚表指针),此时的虚表指针 *((int*)&d)是一个int类型,再把虚表指针类型强转成指针数组类型才能传参

PrintVTable((VFPTR*)(*(int*)((char*)&d+sizeof(Base1)))); 是找到Base2的虚表地址后再解引用找到虚表(直接加2个int字节也能找到base2,考虑Base1可能不单单是2个int大小,这里建议用sizeof(Base1) )

结论: Derive对象Base2虚表中func1是Base2指针ptr2去调用。但是这时ptr2发生切片指针偏移,需要修正。中途就需要修正存储this指针ecx的值

虚函数使用规则

虚函数使用规则:

(1)虚函数在类中声明和类外定义的时候,virtual关键字只在声明时加上,而不能加在在类外实现上

(2)静态成员不可以是虚函数。因为静态成员函数没有this指针,使用类型::成员函数 的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表。

(3)友元函数不属于成员函数,不能成为虚函数

(4)静态成员函数就不能设置为虚函数(原因:静态成员函数与具体对象无关,属于整个类,核心关键是没有隐藏的this指针,可以通过类名::成员函数名 直接调用,此时没有this无法拿到虚表,就无法实现多态,因此不能设置为虚函数)

(5)析构函数建议设置成虚函数,因为有时可能利用多态方式通过基类指针调用子类析构函数(尤其是父类的析构函数强力建议设置为虚函数,这样动态释放父类指针所指的子类对象时,能够达到析构的多态)

 inline函数可以是虚函数吗?
答:可以,不过多态调用的时候编译器就忽略inline属性,这个函数就不再是 inline,因为虚函数要放到虚表中去。

静态成员可以是虚函数吗?
答:不能,因为静态成员函数没有this指针,使用类型::成员函数 的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表。

构造函数可以是虚函数吗?
答:不能,因为对象中的 虚函数表指针 是在 构造函数初始化列表阶段才初始化的 。虚函数的意义是多态,多态调用时到虚函数表中去找,构造函数之前还没初始化,如何去找?

析构函数可以是虚函数吗?什么场景下析构函数是虚函数?

答:可以,并且最好把基类的析构函数定义成虚函数。析构函数名统一会被处理成destructor()

对象访问普通函数快还是虚函数更快?
答:首先如果是普通对象,是一样快的。如果是指针对象或者是引用对象,则调用的普通函数快,因为构成多态,运行时调用虚函数需要到虚函数表中去查找。

虚函数表是在什么阶段生成的,存在哪的?
答: 虚函数表是在编译阶段就生成的 ,一般情况下存在代码段(常量区)的。( 虚函数表指针初始化是指把虚函数表的指针放到对象中去,但生成仍是在编译阶段 )

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/3422.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

雷池社区版新版本功能防绕过人机验证解析

前两天&#xff0c;2024.10.31&#xff0c;雷池社区版更新7.1版本&#xff0c;其中有一个功能&#xff0c;新增请求防重放 更新记录&#xff1a;hhttps://docs.waf-ce.chaitin.cn/zh/%E7%89%88%E6%9C%AC%E6%9B%B4%E6%96%B0%E8%AE%B0%E5%BD%95 仔细研究了这个需求&#xff0c;…

省级-社会保障水平数据(2007-2022年)

社会保障水平是一个综合性的概念&#xff0c;它不仅涉及到一个国家或地区的社会保障制度覆盖范围&#xff0c;还包括了提供的保障种类与水平&#xff0c;以及这些制度在满足公民基本生活需求方面的能力。 2007-2022年省级-社会保障水平数据.zip资源-CSDN文库https://download.…

如何搭建汽车行业AI知识库:定义+好处+方法步骤

在汽车行业&#xff0c;大型车企面临着员工众多、价值链长、技术密集和知识传播难等挑战。如何通过有效的知识沉淀与应用&#xff0c;提升各部门协同效率&#xff0c;快速响应客户咨询&#xff0c;降低销售成本&#xff0c;并开启体系化、可持续性的知识管理建设&#xff0c;成…

【C++篇】数据之林:解读二叉搜索树的优雅结构与运算哲学

文章目录 二叉搜索树详解&#xff1a;基础与基本操作前言第一章&#xff1a;二叉搜索树的概念1.1 二叉搜索树的定义1.1.1 为什么使用二叉搜索树&#xff1f; 第二章&#xff1a;二叉搜索树的性能分析2.1 最佳与最差情况2.1.1 最佳情况2.1.2 最差情况 2.2 平衡树的优势 第三章&a…

如何在Linux下部署自己的ZFile开源网盘

ZFile 项目介绍 ZFile是一个功能强大、灵活的开源网盘系统&#xff0c;为用户提供安全便捷的文件存储和共享方案。 项目概述 ZFile由ZFile, Inc.开发和维护&#xff0c;基于Docusaurus构建。其用户友好的界面支持多种文件存储和共享功能&#xff0c;并具备高度的可定制性和扩…

平替、超越Jira?18 个最佳 Jira 替代方案【开源+免费+付费】

Jira 是一种流行的项目管理工具&#xff0c;被团队广泛用于跟踪和管理他们的任务、问题和项目。 打个不太恰当的比喻&#xff0c;Jira &#xff0c;她就是项目管理家的单反。 如果您正在寻找 Jira 的替代方案&#xff0c;本文介绍了 18个最重要的 Jira 替代方案&#xff0c;可以…

Nuxt.js 应用中的 nitro:build:public-assets 事件钩子详解

title: Nuxt.js 应用中的 nitro:build:public-assets 事件钩子详解 date: 2024/11/5 updated: 2024/11/5 author: cmdragon excerpt: nitro:build:public-assets 是 Nuxt 3 中的一个生命周期钩子,在复制公共资产之后调用。该钩子使开发者能够在构建 Nitro 服务器之前,对…

02_CC2530 + LED流水灯

CC2530 LED流水灯 前言 ​ 在搭建ZigBee定位系统前&#xff0c;先通过几个基础案例熟悉CC2530的一些外设和寄存器编程方式。CC2530基础篇由LED流水灯(按键控制启停、定时器中断方式)、定时器与Delay_ms延时函数、Uart串口通信三章组成。 按键控制启停–通用I/O中断 硬件电…

无线模块的最佳搭档:天线全面选型指南

在无线通信领域&#xff0c;天线的选择至关重要。它不仅影响信号的覆盖范围和传输质量&#xff0c;还直接关系到系统的整体性能。在众多无线模块中&#xff0c;找到合适的天线可以最大化其潜力&#xff0c;确保稳定和高效的数据传输。 在设计适用于射频系统的无线收发设备时&a…

产品思维笔记(一):打造用户喜爱的产品by Marty Cagan

全文摘要 《启示录&#xff1a;打造用户喜爱的产品》是由美国著名产品经理Marty Cagan所著&#xff0c;他曾经是eBay最出色的产品经理之一&#xff0c;也是Google X实验室的创始人之一。在这本书中&#xff0c;他分享了自己的经验和教训&#xff0c;帮助读者更好地理解如何打造…

推荐一款功能强大的电影格式转换器:Total Movie Converter

Coolutils Total Movie Converter(电影格式转换器)是一款可以将超清或者高清蓝光的视频电影进行格式转换的工具&#xff0c;高质量速度快操作简单就是软件最大的亮点&#xff0c;它可以转换几乎所有流行的视频编解码器。 基本简介 Coolutils Total Movie Converter 也可以使视…

掌声响起来——不确定性人工智能与高斯云方法的应用

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

P11232 [CSP-S 2024] 超速检测

P11232 [CSP-S 2024] 超速检测 难度&#xff1a;普及/提高。 考点&#xff1a;二分、贪心。 题意&#xff1a; 题意较长&#xff0c;没有题目大意&#xff0c;否则你也大意。 主干道长度为 L L L&#xff0c;有 n n n 辆车&#xff0c;看做左端点为 0 0 0&#xff0c;第 …

JSP九大内置对象和四大作用域

get和post区别&#xff1a; 比较项 get post 缓存 可以 不可以 收藏为书签 可以 不可以 数据长度 有限制&#xff08;URL 的最大长度是 2048 个字符&#xff09; 无限制 编码类型 application/x-www-form-urlencoded application/x-www-form-urlencoded 或 multi…

【java batik_使用BATIK解析SVG生成PNG图片】

矢量图的介绍及应用场景 矢量图是什么意思&#xff1f; 矢量图&#xff0c;也称为向量图&#xff0c;英文名字是Vector graphics。 矢量图是一种基于矢量的图形&#xff0c;由一系列的线段和曲线组成。由数学公式和算法生成的。这意味着矢量图可以在任何分辨率下清晰地显示&…

针对物联网边缘设备基于EIT的手部手势识别的1D CNN效率增强的组合模型压缩方法

论文标题&#xff1a;Combinative Model Compression Approach for Enhancing 1D CNN Efficiency for EIT-based Hand Gesture Recognition on IoT Edge Devices 中文标题&#xff1a;针对物联网边缘设备基于EIT的手部手势识别的1D CNN效率增强的组合模型压缩方法 作者信息&a…

0.STM32F1移植到F0的各种经验总结

1.结构体的声明需放在函数的最前面 源代码&#xff1a; /*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启USART1的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructu…

【AIGC】ChatGPT提示词Prompt高效编写技巧:逆向拆解OpenAI官方提示词

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AIGC | ChatGPT 文章目录 &#x1f4af;前言&#x1f4af;OpenAI官方提示词的介绍OpenAI官方提示词的结构与组成如何通过分析提示词找到其核心组件 &#x1f4af;OpenAI官方提示词分析案例一&#xff1a;制定教学计划案例二&…

Leetcode 62. 不同路径 动态规划+空间优化

原题链接&#xff1a;Leetcode 62. 不同路径 二维数组&#xff1a; class Solution { public:int uniquePaths(int m, int n) {int res 0;int box[m][n];for (int i 0; i < m; i) {box[i][0] 1;}for (int j 0; j < n; j) {box[0][j] 1;}for (int i 1; i < m;…

app的登录破解 frida jadx

今天收到了一个APP让我研究一下登录 登录已经研究完成 下面则是我的整体思路 为了安全考虑这个app我就不说是那个了 我就说整体的思路 仅供交流学习 严谨非法使用开始进行抓包 手机使用代理连接charles 之后开始点击app登录 进行抓包下面则是我抓到的包 抓包之后j进行改包 也…