多态(C++)
多态
- 一、初识多态
- 概念
- “登场”
- 1>. 多态的构成条件
- 2>. 虚函数
- 3>. 虚函数重写(覆盖)
- 4>. 虚函数重写的两个例外
- 1. 协变 一 基类和派生类虚函数返回值类型不同
- 2. 析构函数重写(基类和派生类析构函数名不同)
- 小结
- 二、延伸知识
- 1>. C++11 override和final
- 拓展一最终类
- 2>. 抽象类
- 概念
- 接口继承和实现继承
- 三、原理
- 1>. 虚函数表(也称虚表)
- 引入
- 分析虚表
- 2>. 多态的原理
- 3>. 拓展 一 静态绑定和动态绑定
- 四、单继承和多继承的虚函数表
- 1>. 单继承中的虚函数表
- 2>. 多继承中的虚函数表
- 1. 多继承
- 2. 菱形继承
- 3. 菱形虚拟继承
一、初识多态
概念
概念:去完成某个行为,当不同的对象去完成时会产生出不同的状态。
eg:
买车票:普通成年人买票时,是全价票;学生买票时,是半价票;
“登场”
1>. 多态的构成条件
- 多态是在不同继承关系的类对象,去调用同一函数,产生不同的行为。
- 在继承的继承上,需要:
- 必须通过基类的指针或者引用调用虚函数。
- 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写 (覆盖)。
eg: “见见猪跑”

2>. 虚函数
虚函数:被virtual修饰的类成员函数

3>. 虚函数重写(覆盖)
虚函数重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型,函数名,参数类型完全相同)。称子类的虚函数重写了父类的虚函数
注意: 重写基类虚函数时,派生类的虚函数可以不加virtual关键字。
eg:

4>. 虚函数重写的两个例外
虚函数重写的要求是派生类虚函数与基类虚函数的返回值类型,函数名,参数类型完全相同(三同)。例外的原因就是不满足三同
1. 协变 一 基类和派生类虚函数返回值类型不同
协变:派生类重写基类虚函数时,与基类虚函数返回值类型不同。
满足协变的条件:返回值类型可以不同,但是返回值必须是父子关系的指针或引用。
test code:
class A
{};
class B : public A
{};class Person
{
public:virtual A* f(){return new A;}
};class Student : public Person
{
public://注意在重写的地方,返回值类型虽然可以不同,但是必须是父子关系,而且同指针或同引用。不能出现基类虚函数返回值类型是父类引用,而派生类重写的虚函数返回类型是派生类的指针virtual B* f(){return new B;}
};
2. 析构函数重写(基类和派生类析构函数名不同)
基类的析构函数为虚函数,则派生类析构函数只要定义,无论是否加virtual关键字,都构成重写。虽然表象函数名不同,但是编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor
test code:
class Person
{
public:virtual ~Person(){cout << "~Person()" << endl;}
};class Student : public Person
{
public:virtual ~Student(){cout << "~Student()" << endl;}
};//只有派生类Student的析构函数重写了Person的析构函数,
//这时delete调用析构时,才能构成多态,保证正确调用析构函数
int main()
{Person* p1 = new Person;Person* p2 = new Student;delete p1;delete p2;return 0;
}//output:
//~Person()
//~Student()
//~Person()
小结
重载、覆盖(重写)、隐藏(重定义)的对比:

二、延伸知识
1>. C++11 override和final
- override:检查派生类虚函数是否重写了基类某个虚函数,如果没有重写报错
test code:
class Car
{
public:virtual void Drive() {}
};class Benz : public Car
{
public:virtual void Drive() override //ok 完成了重写{cout << "Benz-舒适" << endl;}
};
- final:修饰虚函数,表示该虚函数不能再被重写
test code:
class Car
{
public:virtual void Drive() final{}
};class Benz : public Car
{
public:virtual void Drive() //error 原因:final禁止了重写{cout << "Benz-舒适" << endl;}
};
拓展一最终类
当我们想设计不想被继承的类时,有两种方法
方法1 一一 对应C++98
eg1: 隐藏构造函数,当想要创建A对象时,定义一个静态的成员函数
class A
{
public:static A CreateObj(){return A();}
private:A(){}
};class B : public A
{};int main()
{//B bb; //errA::CreateObj();return 0;
}
eg2:隐藏析构函数,当想要创建A对象new一个,释放时定义一个静态的destructor,即可
class A
{
public:
private:~A(){}
};
class B : public A
{};int main()
{//B bb; //errA* p = new A;return 0;
}
方法2 一一 对应C++11
eg:被final修饰的类,被称为最终类,不能被继承
class A final
{
public:
private:
};
class B : public A
{};
2>. 抽象类
概念
- 纯虚函数:虚函数的后面写上 = 0
- 抽象类(接口类):包含纯虚函数的类。
抽象类不能实例化出对象,派生类继承后也不能,只有重写纯虚函数,派生类才可以实例化对象。规范了派生类必须重写。
test code:
class Car
{
public:virtual void Drive() = 0;
};class Benz : public Car
{
public:virtual void Drive(){cout << "Benz-舒适" << endl;}
};class BMW : public Car
{
public:virtual void Drive(){cout << "BMW-好操控" << endl;}
};int main()
{Car* pBenz = new Benz;pBenz->Drive();Car* pBMW = new BMW;pBMW->Drive();return 0;
}
接口继承和实现继承
- 实现继承:普通函数继承。派生类继承基类函数,可以使用,继承的是函数实现。
- 接口继承:虚函数的继承。派生类继承的是虚函数的接口,目的是为了重写,达成多态,继承的是接口。
注意:不实现多态就不要把函数定义成虚函数
三、原理
基于vs2019进行模型分析
1>. 虚函数表(也称虚表)
引入
test code:
//计算Base对象的大小
class Base
{
public:virtual void Func(){cout << "Func()" << endl;}private:int _b = 1;
};int main()
{Base b;cout << sizeof(b) << endl;return 0;
}
//output: 8
代码分析:

分析虚表
test code:
class Base
{
public:virtual void Func1(){cout << "Base::Func1()" << endl;}virtual void Func2(){cout << "Base::Func2()" << endl;}void Func3(){cout << "Base::Func3()" << endl;}private:int _b = 1;
};class Derive : public Base
{
public:virtual void Func1(){cout << "Derive::Func1()" << endl;}
private:int _d = 2;
};int main()
{Base b;Derive d;return 0;
}
通过上面的测试代码,发现一下六点:

2>. 多态的原理
上面分析了很久的虚表,以对虚表的介绍为基础,来分析多态的原理。
test code:
class Person
{
public:virtual void BuyTicket(){cout << "买票-全价" << endl;}
};
class Student : public Person
{
public:virtual void BuyTicket(){cout << "买票-半价" << endl;}
};void Func(Person* p)
{p->BuyTicket();
}int main()
{Person ps;Func(&ps);ps.BuyTicket();Student st;Func(&st);return 0;
}
达到多态,有两个条件:一是虚函数覆盖,一个是对象的指针或引用调用虚函数。
通过下面汇编代码的分析,看出满足多态以后的函数调用,不是在编译时确定的,是运行起来以后到对象中去找的。不满足多态的函数调用是编译时确认好的。


3>. 拓展 一 静态绑定和动态绑定
- 静态绑定(前期绑定):在程序编译期间确定了程序的行为,也称静态多态。 eg:函数重载
- 动态绑定(后期绑定):是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称动态多态
上一个汇编代码的例子,就很好的解释了静态绑定和动态绑定
四、单继承和多继承的虚函数表
1>. 单继承中的虚函数表
test code:
class Base
{
public:virtual void func1(){cout << "Base::func1()" << endl;}virtual void func2(){cout << "Base::func2()" << endl;}
private:int _b;
};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 _d;
};int main()
{Base b;Derive d;return 0;
}
观察下图中监视窗口,发现派生类的虚函数func3和func4看不见。 原因:编译器的监视窗口隐藏了这个两个函数。

在分析虚表这一小节内容时,在第四小点说到,虚表本质是一个存虚函数指针的指针数组,一般情况这个数组最后放一个nullptr,通过监视窗口查看不了派生类对象d的虚表,下面我们借用nullptr的帮助,使用代码打印出虚表中的函数
PrintVTable_code:打印虚表的代码
注意:这个打印虚表的代码经常崩溃,编译器对虚表的处理不干净,虚表最后没有放nullptr的指针,导致越界。我们只需要清理解决方案,重新编译即可。
typedef void(*VFPtr) (); //函数指针
void PrintVTable(VFPtr VTable[])
{//依次取虚表中的虚函数指针打印并调用。cout << "虚表地址>" << VTable << endl;for (size_t i = 0; VTable[i] != nullptr; i++){printf("第%d个虚函数地址:0X%p,->", i, VTable[i]);VFPtr f = VTable[i];f(); //调用方便看存的是那个函数}cout << endl;
}
注意:传参调用PrintVTable的思路
int main()
{Base b;Derive d;//思路:取b、d对象的头4个字节,就是虚表的指针。//以b对象讲解//1.先取b的地址,强转成int*的指针//2.再解引用取值,就取到b对象头4个字节的值,也就是指向虚表的指针//3.再强转成VFPtr*,因为虚表就是一个存VFPtr类型(虚函数指针类型)的数组//4.虚表指针传递给PrintVTable进行打印虚表VFPtr* vTable_b = (VFPtr*)(*(int*)&b);PrintVTable(vTable_b);VFPtr* vTable_d = (VFPtr*)(*(int*)&d);PrintVTable(vTable_d);return 0;
}
上面代码打印出虚表中虚函数的结果分析:

2>. 多继承中的虚函数表
1. 多继承
test code:
class Base1
{
public:virtual void func1(){cout << "Base1::func1()" << endl;}virtual void func2(){cout << "Base1::func2()" << endl;}
private:int _b1 = 1;
};class Base2
{
public:virtual void func1(){cout << "Base2::func1()" << endl;}virtual void func2(){cout << "Base2::func2()" << endl;}
private:int _b2 = 1;
};class Derive : public Base1, public Base2
{
public:virtual void func1(){cout << "Derive::func1()" << endl;}virtual void func3(){cout << "Derive::func3()" << endl;}
private:int _d = 2;
};typedef void(*VFPtr) (); //函数指针
void PrintVTable(VFPtr vTable[])
{//依次取虚表中的虚函数指针打印并调用。cout << "虚表地址>" << vTable << endl;for (size_t i = 0; vTable[i] != nullptr; i++){printf("第%d个虚函数地址:0X%p,->", i, vTable[i]);VFPtr f = vTable[i];f(); //调用方便看存的是那个函数}cout << endl;
}int main()
{Derive d;VFPtr* vTable_b1 = (VFPtr*)(*(int*)&d);PrintVTable(vTable_b1);//(char*)&d 这里一定要注意强转,否则+1,就是加一个Derive的大小VFPtr* vTable_b2 = (VFPtr*)(*(int*)((char*)&d + sizeof(Base1)));PrintVTable(vTable_b2);return 0;
}
多继承测试代码展开分析:

2. 菱形继承
test code:
#include<iostream>
using namespace std;
class A
{
public:virtual void fun1(){cout << "A::fun1()" << endl;}int _a = 0;
};class B : public A
{
public:virtual void fun1(){cout << "B::fun1()" << endl;}virtual void fun2(){cout << "B::fun2()" << endl;}int _b = 0;
};class C : public A
{
public:virtual void fun1(){cout << "C::fun1()" << endl;}virtual void fun2(){cout << "C::fun2()" << endl;}int _c = 0;
};
class D : public B, public C
{
public:virtual void fun2(){cout << "D::fun2()" << endl;}virtual void fun3(){cout << "D::fun3()" << endl;}
};typedef void(*VFPtr) (); //函数指针
void PrintVTable(VFPtr vTable[])
{//依次取虚表中的虚函数指针打印并调用。cout << "虚表地址>" << vTable << endl;for (size_t i = 0; vTable[i] != nullptr; i++){printf("第%d个虚函数地址:0X%p,->", i, vTable[i]);VFPtr f = vTable[i];f(); //调用方便看存的是那个函数}cout << endl;
}int main()
{D d;VFPtr* vTable_d = (VFPtr*)(*(int*)&d);PrintVTable(vTable_d);C* ptr1 = &d;VFPtr* vTable_c = (VFPtr*)(*(int*)ptr1);PrintVTable(vTable_c);return 0;
}
菱形继承测试代码展开分析:(菱形继承和多继承没有什么大的区别)

3. 菱形虚拟继承
- 只有A类有虚函数
test code:
class A
{
public:virtual void fun1(){cout << "A::fun1()" << endl;}
public:int _a = 0;
};class B : virtual public A
{
public:int _b = 0;
};class C : virtual public A
{
public:int _c = 0;
};
class D : public B, public C
{
public:int _d;
};int main()
{D d;d.B::_a = 1;d.C::_a = 2;d._b = 3;d._c = 4;d._d = 5;return 0;
}
代码内存分析:

- B和C完成对A的虚函数重写
test code:
class A
{
public:virtual void fun1(){cout << "A::fun1()" << endl;}
public:int _a = 0;
};class B : virtual public A
{
public:virtual void fun1(){cout << "B::fun1()" << endl;}
public:int _b = 0;
};class C : virtual public A
{
public:virtual void fun1(){cout << "C::fun1()" << endl;}
public:int _c = 0;
};
class D : public B, public C
{
public:int _d;
};int main()
{D d;d.B::_a = 1;d.C::_a = 2;d._b = 3;d._c = 4;d._d = 5;return 0;
}
运行结果:
编译报错
原因:因为整个对象只有A一张虚表是共享的,B要重写,C也要重写。不明确到底重写谁的。
解决办法:让D重写即可。当然B和C的重写并不是没有意义,如果定义B和C类型的对象,单独使用还是有意义的。
- 只有A类有虚函数 + B和C有单独的虚函数
test code:
class A
{
public:virtual void func1(){cout << "A::func1" << endl;}
public:int _a;
};class B : virtual public A
{
public:virtual void func1(){cout << "B::func1" << endl;}virtual void func2(){cout << "B::func2" << endl;}
public:int _b;
};class C : virtual public A
{
public:virtual void func1(){cout << "C::func1" << endl;}virtual void func2(){cout << "C::func2" << endl;}
public:int _c;
};class D : public B, public C
{
public:virtual void func1(){cout << "D::func1" << endl;}public:int _d;
};int main()
{D d;d.B::_a = 1;d.C::_a = 2;d._b = 3;d._c = 4;d._d = 5;return 0;
}
代码内存分析:

- 如果在第三点的基础上,D类也有自己的虚函数,那么将放哪里?
test code:
class A
{
public:virtual void func1(){cout << "A::func1" << endl;}
public:int _a;
};class B : virtual public A
{
public:virtual void func1(){cout << "B::func1" << endl;}virtual void func2(){cout << "B::func2" << endl;}
public:int _b;
};class C : virtual public A
{
public:virtual void func1(){cout << "C::func1" << endl;}virtual void func2(){cout << "C::func2" << endl;}
public:int _c;
};class D : public B, public C
{
public:virtual void func1(){cout << "D::func1" << endl;}virtual void func3(){cout << "D::func3" << endl;}public:int _d;
};typedef void(*VFPtr) (); //函数指针
void PrintVTable(VFPtr vTable[])
{//依次取虚表中的虚函数指针打印并调用。cout << "虚表地址>" << vTable << endl;for (size_t i = 0; vTable[i] != nullptr; i++){printf("第%d个虚函数地址:0X%p,->", i, vTable[i]);VFPtr f = vTable[i];f(); //调用方便看存的是那个函数}cout << endl;
}int main()
{D d;VFPtr* vTable_d = (VFPtr*)(*(int*)&d); //B的虚表PrintVTable(vTable_d);C* ptr1 = &d;VFPtr* vTable_c = (VFPtr*)(*(int*)ptr1); //C的虚表PrintVTable(vTable_c);A* ptr2 = &d;VFPtr* vTable_a = (VFPtr*)(*(int*)ptr2); //A的虚表PrintVTable(vTable_a);return 0;
}
运行结果:由结果得到D类自己的虚函数放在第一张虚表中

相关文章:
多态(C++)
多态 一、初识多态概念“登场”1>. 多态的构成条件2>. 虚函数3>. 虚函数重写(覆盖)4>. 虚函数重写的两个例外1. 协变 一 基类和派生类虚函数返回值类型不同2. 析构函数重写(基类和派生类析构函数名不同) 小结 二、延伸…...
算法leetcode|73. 矩阵置零(rust重拳出击)
文章目录 73. 矩阵置零:样例 1:样例 2:提示:进阶: 分析:题解:rust:go:c:python:java: 73. 矩阵置零: 给定一个 m x n 的矩…...
axios 二次封装
axios 二次封装 基本上每一个项目开发,都必须要二次封装 axios。主要是为了减少重复性工作,不可能每一次发起新请求时,都要重新配置请求域名、请求头 Content-Type、Token 等信息。所以需要把公用的部分都封装成一个函数,每次调用…...
Rust安全之数值
文章目录 数值溢出 数值溢出 编译通过,运行失败 cargo run 1 fn main() {let mut arg std::env::args().skip(1).map(|x| x.parse::<i32>().unwrap()).next().unwrap();let m_i i32::MAX - 1;let a m_i arg;println!("{:?}", a); }thread main panicked…...
4种方法实现html 页面内锚点定位及跳转
使用scrollIntoView进行锚点定位效果 不知道你有没有遇到这样的需求:锚点定位?进入页面某个元素需要出现在可视区?…这一类的需求归根结底就是处理元素与可视区域的关系。我接触了很多前端小伙伴,实现的方式有各种各样的ÿ…...
gitlab配置备忘
版本 gitlab 14.6.2 gitlab备份上传到阿里云oss ### Backup Settings ###! Docs: https://docs.gitlab.com/omnibus/settings/backups.html# gitlab_rails[manage_backup_path] true # gitlab_rails[backup_path] "/var/opt/gitlab/backups"###! Docs: https://…...
基于Centos搭建k8s仓库
系统环境: Red Hat Enterprise Linux 9.1 (Plow) Kernel: Linux 5.14.0-162.6.1.el9_1.x86_64 主机名地址master192.168.19.128node01192.168.19.129node02192.168.19.130 目录 1、关闭防火墙,关闭SElinxu ,开启时间同步服务 2、关…...
浅谈泛在电力物联网发展形态与技术挑战
安科瑞 华楠 摘 要:泛在电力物联网是当前智能电网发展的一个方向。首先,总结了泛在电力物联网的主要作用和价值体现;其次,从智能电网各个环节概述了物联网技术在电力领域的已有研究和应用基础;进而,构思并…...
git reset --soft 用法
git reset --soft 是 Git 命令中的一个选项,它用于取消之前的提交,并将取消的更改保留在暂存区。这允许您重新组织提交历史或将更改合并到一个新的提交中,而不影响暂存区和工作目录中的更改。 这个命令的语法是: git reset --so…...
哪些测试仪器可以用于检测静电中和设备的性能
静电设备性能测试通常需要使用一些专门的仪器来进行。以下是一些常见的静电设备性能测试仪器: 1. 静电电压测试仪:用于测量物体表面的静电电压。它通常可以测量正负电压,并具有高精度和快速响应的特点。 2. 静电电荷仪:用于测量物…...
浅析 GlusterFS 与 JuiceFS 的架构异同
在进行分布式文件存储解决方案的选型时,GlusterFS 无疑是一个不可忽视的考虑对象。作为一款开源的软件定义分布式存储解决方案,GlusterFS 能够在单个集群中支持高达 PiB 级别的数据存储。自从首次发布以来,已经有超过十年的发展历程。目前&am…...
ARM开发,stm32mp157a-A7核PWM实验(驱动蜂鸣器,风扇,马达工作)
1.分析框图; 2.比较捕获寄存器(产生PWM方波); 工作原理: 1、系统提供一个时钟源209MHZ,需要通过分频器进行分频,设置分频器值为209分频; 2、当定时器启动之后,自动重载…...
群狼调研(长沙眼镜店神秘顾客)|消费者需求研究方案
本文由群狼调研(长沙品牌调研)出品,欢迎转载,请注明出处。消费者需求研究方案是在开展研究之前制定的计划,用于指导研究的设计、实施和分析。以下是一个可能的消费者需求研究方案的大致框架: 1. 研究目标和问题: • …...
电脑入门:宽带路由器常见故障排除技巧
宽带路由器在企业网络中的应用是相当广泛的,在运行的过程中出现故障是在所难免的,虽然故障现象多种多样,引起故障发生的原因也不尽相同,但从大体上可以把这些故障分为硬件故障和软件故障,具体来说就是一些网络连接性问题、配置文件选项问题以及网络协议问题等。 由于路由器…...
基于云原生网关的流量防护实践
作者:涂鸦 背景 在分布式系统架构中,每个请求都会经过很多层处理,比如从入口网关再到 Web Server 再到服务之间的调用,再到服务访问缓存或 DB 等存储。在下图流量防护体系中,我们通常遵循流量漏斗原则进行流量防护。…...
开源与云计算:新的合作模式
🌷🍁 博主猫头虎 带您 Go to New World.✨🍁 🦄 博客首页——猫头虎的博客🎐 🐳《面试题大全专栏》 文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~🌺 &a…...
前端需要理解的跨平台知识
混合开发是指使用多种开发模开发App的一种开发模式,涉及到两大类技术:原生 Native、Web H5。原生 Native 主要指 iOS(Objective C)、Android(Java),原生开发效率较低,开发完成需要重…...
《基于 Vue 组件库 的 Webpack5 配置》3.将 CSS 提取到单独的文件
使用 webpack 插件 mini-css-extract-plugin 需要额外安装 npm i mini-css-extract-pluginlatest -D; 同时打包 js 和 css 文件时,可参考 entry 高级用法; package.json 的配置如下 const { VueLoaderPlugin } require(vue-loader); // 可…...
2023CCF图形学启明星计划夏令营感想记录
这篇就是纯日记了,想记录一下参加这个夏令营的感想,中间的一些过程,毕竟这对我来说算是一段难忘的经历。 一、了解到的渠道 我个人是比较喜欢图形渲染的,之前也学过GAMES的课程,然后偶然的一天,GAMES101里…...
如何解决“缺失msvcp110.dll”错误,msvcp110.dll丢失要怎样才能修复
今天,我将为大家分享关于电脑提示msvcp110.dll丢失的3种修复方法。希望这些方法能帮助到正在遇到这个问题的朋友们。 首先,我们来了解一下msvcp110.dll文件的作用。msvcp110.dll是Microsoft Visual C 2010 Redistributable Package的一部分,…...
Transformer解码器实战:用PyTorch手写Masked Self-Attention(附避坑指南)
Transformer解码器实战:用PyTorch手写Masked Self-Attention(附避坑指南) 1. 为什么需要Masked Self-Attention 在文本生成任务中,模型需要遵循自回归特性——即生成当前词时只能依赖已生成的词。想象你正在玩文字接龙游戏&#x…...
3个关键场景:如何用Awesome Claude Code打造你的AI开发工作流
3个关键场景:如何用Awesome Claude Code打造你的AI开发工作流 【免费下载链接】awesome-claude-code A curated list of awesome commands, files, and workflows for Claude Code 项目地址: https://gitcode.com/GitHub_Trending/aw/awesome-claude-code 你…...
Qwen3-0.6B-FP8逻辑推理能力实测:解决经典谜题与数学问题
Qwen3-0.6B-FP8逻辑推理能力实测:解决经典谜题与数学问题 最近在尝试一些轻量级的AI模型,发现Qwen3-0.6B-FP8这个小家伙挺有意思。它体积不大,但官方宣称在逻辑推理方面有不错的表现。这让我很好奇,一个只有6亿参数的模型&#x…...
SMUDebugTool硬件调试实战:如何通过系统管理单元实现AMD Ryzen处理器深度优化
SMUDebugTool硬件调试实战:如何通过系统管理单元实现AMD Ryzen处理器深度优化 【免费下载链接】SMUDebugTool A dedicated tool to help write/read various parameters of Ryzen-based systems, such as manual overclock, SMU, PCI, CPUID, MSR and Power Table. …...
JY61P陀螺仪串口数据解析实战:从协议到STM32代码实现
1. JY61P陀螺仪模块初探 第一次拿到JY61P这个六轴姿态传感器时,我下意识以为它和常见的MPU6050差不多。但实际用下来发现,这个国产模块在精度和易用性上都有明显优势。最让我惊喜的是它支持串口通信,完美避开了I2C协议那些令人头疼的时序问题…...
深入解析FOC控制中的Clark/Park变换及其Matplotlib动态仿真实现
1. 从三相交流电到FOC控制的基础认知 第一次接触电机控制时,看到那些复杂的坐标变换公式确实让人头疼。但后来我发现,理解FOC(磁场定向控制)的核心,关键在于抓住两个关键点:为什么要做坐标变换和变换后能解…...
Axure RP 中文语言包:3分钟消除语言障碍,释放原型设计效率
Axure RP 中文语言包:3分钟消除语言障碍,释放原型设计效率 【免费下载链接】axure-cn Chinese language file for Axure RP. Axure RP 简体中文语言包,不定期更新。支持 Axure 9、Axure 10。 项目地址: https://gitcode.com/gh_mirrors/ax/…...
【chat】Verilog命名规范实战指南:从文件到模块的优雅编码
1. Verilog命名规范的重要性 刚开始接触Verilog的时候,我总觉得命名规范是个可有可无的东西。直到有一次接手同事的代码,看到一堆乱七八糟的命名,才深刻体会到规范的重要性。那感觉就像走进一个没有标签的仓库,想找什么都得一个个…...
CK3M多轴运动控制器实战:EtherCAT总线伺服系统从零配置全解析
1. CK3M控制器与EtherCAT系统初识 第一次接触CK3M多轴运动控制器时,我完全被它强大的功能震撼到了。这款控制器就像工业自动化领域的"大脑",能够同时协调多个伺服电机精准运动。而EtherCAT总线技术则是连接这个大脑与各个执行机构(…...
新中大SE系统反月结避坑指南:从月结修复到重新记账的完整操作解析
新中大SE系统月结异常处理实战手册:从错误回溯到数据修正的全流程精解 财务系统的月结操作如同会计周期的"收官之战",一旦发现历史凭证存在错误,往往让使用者陷入两难境地——既要确保数据准确性,又担心操作不当引发连锁…...
