【浅尝C++】继承机制=>虚基表/菱形虚继承/继承的概念、定义/基类与派生类对象赋值转换/派生类的默认成员函数等详解
🏠专栏介绍:浅尝C++专栏是用于记录C++语法基础、STL及内存剖析等。
🎯每日格言:每日努力一点点,技术变化看得见。
文章目录
- 继承的概念及定义
- 继承的概念
- 继承的定义
- 定义格式
- 继承关系与访问限定符
- 基类和派生类对象赋值转换
- 继承中的作用域
- 派生类的默认成员函数
- 继承与友元
- 继承与静态成员
- 复杂的菱形继承及菱形虚拟继承
继承的概念及定义
继承的概念
我们生活中也有继承的例子,例如:小明继承了孙老师傅做拉面的手艺。继承就是一种延续、复用的方式。C++为了提高代码的可复用性,引入了继承机制,概念如下↓↓↓
继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。
继承的定义
定义格式
下图演示的继承的格式,其中Person是父类,也称作基类;Student是子类,也称作派生类。
下面给出代码示例(下面代码中,Student类继承父类Person)↓↓↓
#include <iostream>
using namespace std;class Person
{
public:void Show(){cout << _name << " " << _age << endl;}
protected:string _name = "jammingpro"; //姓名int _age = 18; //年龄
};class Student : public Person
{
private:int _stuId;
};int main()
{Person p;Student s;p.Show();s.Show();return 0;
}
上面代码中,Student继承父类Person的成员(成员函数+成员变量)后,这些成员都变成了子类的一部分。这里的Student复用了Person的成员。通过监视窗口可以看到,Student中也有自己的_name、_age成员变量。
继承关系与访问限定符
在C++的继承机制中,包含3种继承方式及3种类访问限定符(如下图所示),下面将分别介绍它们。
我们在学习类和对象时,就已经接触过访问限定符。其中public成员可以在类外访问,而protected与private成员不能在类外访问。但这里的protected和private在继承时是有区别的:
●如果父类愿意让自己的成员被外界访问并愿意让子类继承,则定义为public的;
●如果父类希望自己的成员不被外界访问而愿意让子类继承,则需要定义为protected;
●如果父类不希望自己的成员被外界访问、被继承,则需要定义为private的。
父类中的访问限定符表示父类愿不愿意让子类继承,而继承方式则可以让子类缩小父类成员的访问权限,但不能放大父类成员的访问权限。
父类成员/子类继承方式 | public继承 | protected继承 | private继承 |
---|---|---|---|
父类的public成员 | 变为子类的public成员 | 变为子类protected成员 | 变为子类的private成员 |
父类的protected成员 | 变为子类的protected成员 | 变为子类的protected成员 | 变为子类的private成员 |
父类的private成员 | 子类不可见 | 子类不可见 | 子类不可见 |
总结
-
基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面
都不能去访问它。 -
基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
-
实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式等于
Min{成员在基类的访问限定符,继承方式}
,其中,public>protected>private。 -
使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
-
在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。
针对于总结中的第一点,父类private成员实际上还是被子类继承了,只是子类无法访问,下面使用代码验证↓↓↓
#include <iostream>
using namespace std;class Base
{
private:int _base;
};class Son : public Base
{}int main()
{Son s;return 0;
}
基类和派生类对象赋值转换
●派生类对象可以赋值给基类的对象/基类的指针/基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。
下面给出代码示例↓↓↓
#include <iostream>
using namespace std;class Person
{
public:Person(){}Person(const string& name, const char& sex, const int& age):_name(name),_sex(sex),_age(age){}
protected:string _name;char _sex;int _age;
};class Student : public Person
{
public:Student(const string& name, const char& sex, const int& age, const int& stuId):Person(name, sex, age),_stuId(stuId){}
private:int _stuId;
};int main()
{Student s("Jammingpro", 'M', 18, 123456);Person p;p=s;return 0;
}
从监视窗口可以看到,Person对象保存了Student对象的父类成员部分,而舍弃了子类自有成员,这就是切片。
●基类对象不能赋值给派生类对象。(基类对象无法用于构造派生类对象,也无法使用派生类对象的拷贝赋值函数;但可以显示提供派生类赋值给基类的operator=实现)
★ps:由于派生类中的成员函数、成员对象一般情况下都会多于基类,如果基类直接赋值给派生类会导致部分成员数值不确定。因此,C++默认不提供基类赋值给派生类。
●基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针指向派生类对象时才是安全的。
下面代码演示了基类赋值给派生类指针,派生类赋值给基类指针↓↓↓
#include <iostream>
using namespace std;class Person
{
public:Person(){}Person(const string& name, const char& sex, const int& age):_name(name), _sex(sex), _age(age){}string _name;char _sex;int _age;
};class Student : public Person
{
public:Student(const string& name, const char& sex, const int& age, const int& stuId):Person(name, sex, age), _stuId(stuId){}int _stuId;
};int main()
{Student s("Jammingpro", 'M', 18, 123456);Person p("xiaoming", 'M', 20);Person* p_s = &s;//安全Student* s_p = (Student*) & p;//不安全cout << s_p->_stuId << endl;return 0;
}
为什么说,Student* s_p = (Student*) & p;
是不安全的呢?由于Person对象中没有申请_stuId的空间,但在Student*类型看来,它认为它指向的对象有_stuId成员。如果用户访问了s_p->_stuId可能会因为内存非法访问而报错。
继承中的作用域
- 在继承体系中基类和派生类都有独立的作用域。
- 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)
- 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏(而不需要返回值相同,或是参数列表相同)。
- 注意在实际中在继承体系里面最好不要定义同名的成员。
下面给出派生类成员变量与基类成员变量同名的例子↓↓↓
下面代码中,由于Son类中的成员变量与Base类中的成员变量重名,构成了隐藏。如果使用Son s; cout << s._name << endl;
,则只能访问到Son对象中的成员变量,而无法访问到父类中的_name成员变量。若需要访问父类的_name成员变量,则可以使用类型+类作用域符号::
来访问,即s.Base::_name
。
#include <iostream>
using namespace std;class Base
{
public:string _name = "Jammingpro";
};class Son : public Base
{
public:string _name = "xiaoming";
};int main()
{Son s;cout << s._name << endl;cout << s.Base::_name << endl;return 0;
}
下面给出派生类成员函数与基类成员函数同名的例子↓↓↓这里与成员变量同名的情况相同,同名成员函数也会构成隐藏关系,如果需要访问父类的同名成员函数,需要使用类名+类作用域运算符::
。
#include <iostream>
using namespace std;class Base
{
public:void Show(){cout << "I am _Base" << endl;}
};class Son : public Base
{
public:void Show(){cout << "I am _Son" << endl;}
};int main()
{Son s;s.Show();s.Base::Show();return 0;
}
★( ఠൠఠ )ノtest:下面的两个同名函数(函数名相同,参数列表不同)分别属于基类和派生类,它们构成的关系是隐藏还是函数重载呢?
#include <iostream>
using namespace std;class Base
{
public:void print(char ch){cout << "Base->" << ch << endl;}
};class Son : public Base
{
public:void print(int num){cout << "Son->" << num << endl;}
};int main()
{Son s;s.print('A');return 0;
}
Base中的成员函数比Son中的成员函数更匹配(不需要隐式类型转换),而这里还是调用Son中的成员函数,说明两者构成的关系是隐藏,而不是函数重载。这里要注意:在继承关系中,派生类与基类只要存在同名函数(不管参数列表、返回值是否相同),都是隐藏关系。
派生类的默认成员函数
6个默认成员函数,“默认”的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类中,这几个成员函数是如何生成的呢?
- 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
基类提供默认构造函数的情况
派生类在构造时,会自动调用基类的构造函数↓↓↓
#include <iostream>
using namespace std;class Base
{
public:Base(){cout << "Base() is called" << endl;}
};class Son : public Base
{
public:Son(){cout << "Son() is called" << endl;}
};int main()
{Son s;return 0;
}
基类没有提供默认构造函数的情况
基类没有提供默认构造时,子类必须在初始化参数列表中显示调用基类的构造函数,否则会报错。
#include <iostream>
using namespace std;class Base
{
public:Base(int b):_b(b){cout << "Base(int b) is called" << endl;}
private:int _b
};class Son : public Base
{
public:Son(int b):Base(b){cout << "Son() is called" << endl;}
};int main()
{Son s(5);return 0;
}
- 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
#include <iostream>
using namespace std;class Base
{
public:Base(int b):_b(b){}Base(const Base& b){_b = b._b;cout << "Base(const Base& b) is called" << endl;}
private:int _b;
};class Son : public Base
{
public:Son(int s, int b):_s(s),Base(b){}Son(const Son& s):Base(s){_s = s._s;cout << "Son(const Son& s) is called" << endl;}
private:int _s;
};int main()
{Son s(55, 66);Son s2(s);return 0;
}
如果我们将上面代码中显示调用基类构造函数的代码去掉,则会出现如下报错↓↓↓
★ps:对于上面的报错,虽然可以通过给基类提供默认构造函数解决,但却无法完成子类中的基类成员的拷贝操作。
- 派生类的operator=可以调用基类的operator=完成基类的复制,这样可以避免代码冗余(这里派生类调用基类的operator=不是必须的,只是因为基类已经实现了该操作,派生类不必再重复编写相同内容)。
下面代码演示了派生类调用基类operator=函数带来的代码简化↓↓↓
#define _CRT_SECURE_NO_WARNINGS 1#include <iostream>
#include <string>
#include <cstring>
using namespace std;class Person
{
public:Person(const string& name, const int& age, const char& gender):_name(name),_age(age),_gender(gender){}Person& operator=(const Person& p){_name = p._name;_age = p._age;_gender = p._gender;return *this;}void Show(){cout << "My name is " << _name << ", I am " << _age << " years old, I am a " << (_gender == 'M' ? " boy " : "girl") << endl;}
private:string _name;int _age;char _gender;
};class Student : public Person
{
public:Student(const string& name, const int& age, const char& gender, const char* detail):Person(name, age, gender){_detail = new char[strlen(detail) + 1];strcpy(_detail, detail);}Student& operator=(const Student& s){Person::operator=(s);char* detail = new char[strlen(s._detail)];return *this;}void Show(){Person::Show();cout << "My detail infomation is " << _detail << endl;}~Student(){delete[] _detail;}
private:char* _detail;
};int main()
{Student s("Jammingpro", 18, 'M', "He is good at coding");Student copy = s;s.Show();
}
-
派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
-
派生类对象初始化先调用基类构造再调派生类构造。
下面代码演示了基类与派生类的构造与析构顺序↓↓↓
#include <iostream>
using namespace std;class Base
{
public:Base(){cout << "Base() is called" << endl;}~Base(){cout << "~Base() is deleted obj" << endl;}
};class Son : public Base
{
public:Son(){cout << "Son is called" << endl;}~Son(){cout << "~Son() is deleted obj" << endl;}
};int main()
{Son s;return 0;
}
- 编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加virtual的情况下,子类析构函数和父类析构函数构成隐藏关系。
★ps:关于virtual关键字将于多态中讲解
继承与友元
友元关系不能不继承,也就是说:基类的友元不能访问派生类的私有和保护成员。
下面代码演示了友元关系无法继承↓↓↓
#include <iostream>
using namespace std;class Base
{friend void print();
private:int _base = 88;
};class Son : Base
{
private:int _son = 66;
};void print()
{Base b;cout << b._base << endl;Son s;cout << s._son << endl;
}int main()
{print();return 0;
}
继承与静态成员
基类定义了static静态成员,则整个继承体系中只能有一个这样的成员。无论派生出多少多少个子类,都只有一个static成员实例。
#include <iostream>
using namespace std;class Base
{
public:static int val;
};//静态非const成员变量需要在类外初始化
int Base::val = 66;class Son : public Base
{
};class GrandSon : Son
{
};int main()
{cout << &Base::val << endl;cout << &Son::val << endl;cout << &GrandSon::val << endl;return 0;
}
复杂的菱形继承及菱形虚拟继承
单继承:一个子类只有一个直接父类时称这个继承关系为单继承
下面是一份单继承的代码↓↓↓
#include <iostream>
using namespace std;class Base
{
public:void base_func(){}int _base;
};class Son : public Base
{
public:void son_func(){}int _son;
};class GrandSon : public Son
{
public:void gs_func(){}int _gs;
};int main()
{GrandSon gs;cout << &gs << endl;gs._base = 1;cout << &gs._base << endl;gs._son = 2;cout << &gs._son << endl;gs._gs = 3;cout << &gs._gs << endl;cout << "===================================" << endl;Son s;cout << &s << endl;s._base = 1;cout << &s._base << endl;s._son = 2;cout << &s._son << endl;return 0;
}
上述代码调试时,通过监视窗口查看结果如下图所示。我们可以发现,GrandSon对象保存了其祖先类Son、Base的成员变量。Son对象保存了其基类成员变量。
下图为GrandSon对象的存储情况↓↓↓
通过分析上面的执行结果,可以得出如下结论:再单继承中,某个类的成员变量放置于类空间最后,该成员变量前放置的是直接父类,再往上是爷爷类,以此类推。类对象的地址,与最顶层的祖先的成员变量地址相同。
多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承
下面给出多继承的演示代码↓↓↓
#include <iostream>
using namespace std;class Base1
{
public:void base1_func(){}int _base1;
};class Base2
{
public:void base2_func(){}int _base2;
};class Son : public Base1, public Base2
{
public:void son_func(){}int _son;
};int main()
{Son s;cout << &s << endl;s._base1 = 1;cout << &s._base1 << endl;s._base2 = 2;cout << &s._base2 << endl;s._son = 3;cout << &s._son << endl;return 0;
}
如果Son先继承Base1再继承Base2,则会将Base1的成员变量放在前面,后继承的Base2的成员变量放在后面。
如果将Son先继承Base2,再继承Base1呢?
由上面的执行结果可知,先继承的基类的成员变量放置于类对象的前面位置,后即成的基类的成员变量放置于类对象的后面位置,类自身的成员变量放置于最后。
菱形继承:菱形继承是多继承的一种特殊情况
下面给出多继承的演示代码↓↓↓
#include <iostream>
using namespace std;class Share
{
public:void share_func(){}int _share;
};class Base1 : Share
{
public:void base1_func(){}int _base1;
};class Base2 : Share
{
public:void base2_func(){}int _base2;
};class Son : public Base1, public Base2
{
public:void son_func(){}int _son;
};int main()
{Son s;s._base1 = 1;s._base2 = 2;s._son = 3;return 0;
}
在上述代码的监视窗口可以看出菱形继承有数据冗余和二义性的问题。s中继承了两份Share类的成员变量_share。
C++中为了避免菱形继承导致的数据冗余和二义性,它引入了虚拟继承。虚拟继承可以解决菱形继承的二义性和数据冗余的问题。下面给出修改后的代码(引入虚拟继承的代码)↓↓↓
#include <iostream>
using namespace std;class Share
{
public:void share_func(){}int _share;
};class Base1 : virtual public Share
{
public:void base1_func(){}int _base1;
};class Base2 : virtual public Share
{
public:void base2_func(){}int _base2;
};class Son : public Base1, public Base2
{
public:void son_func(){}int _son;
};int main()
{Son s;cout << &s << endl;s._share = 0;cout << &s._share << endl;s._base1 = 1;cout << &s._base1 << endl;s._base2 = 2;cout << &s._base2 << endl;s._son = 3;cout << &s._son << endl;return 0;
}
这里是通过了两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的Share。
🎈欢迎进入浅尝C++专栏,查看更多文章。
如果上述内容有任何问题,欢迎在下方留言区指正b( ̄▽ ̄)d
相关文章:

【浅尝C++】继承机制=>虚基表/菱形虚继承/继承的概念、定义/基类与派生类对象赋值转换/派生类的默认成员函数等详解
🏠专栏介绍:浅尝C专栏是用于记录C语法基础、STL及内存剖析等。 🎯每日格言:每日努力一点点,技术变化看得见。 文章目录 继承的概念及定义继承的概念继承的定义定义格式继承关系与访问限定符 基类和派生类对象赋值转换继…...
tomcat中的web项目配置指引
文章目录 目录结构I server.xml 配置文件1.1 Host标签1.2 contex标签1.3 server.xml 的端口配置1.4 appBase和docBase的区别1.5 Engine标签1.6 Connector标签II Tomcat应用的配置2.1 配置虚拟路径2.2 配置连接数2.3 使用线程池2.4 配置内存大小III 预备知识...

如果你正在投简历,一定要试试这款AI工具!
今天给大家分享一款AI简历神器 - BitBitFly AI 简历助手,这个工具可以帮助大家快速、精准投简历,并且提供职位匹配度分析报告,提供专业优化简历建议提高简历和职位匹配度,轻松拿下offer。 如果你在找工作的时候遇到以下问题&…...

Unity:2D SpriteShape
1.1 简介 Sprite Shape 可以很灵活的更改sprite的轮廓。比如: 它由两部分组成:Sprite Shape Profile、Sprite Shape Controller,需要导入2D Sprite Shape Package. 1.1.1 Sprite导入要求 Texture Type - ‘Sprite (2D and UI)’.Sprite Mo…...

Web大并发集群部署之集群介绍
一、传统web访问模型 传统web访问模型完成一次请求的步骤 1)用户发起请求 2)服务器接受请求 3)服务器处理请求(压力最大) 4)服务器响应请求 传统模型缺点 单点故障; 单台服务器资源有限&…...

Linux_进程的优先级环境变量上下文切换
文章目录 一、进程的优先级二、进程的四个重要概念三、上下文切换四、环境变量4.1 查看当前shell环境下的环境变量与内容 一、进程的优先级 什么是优先级? 指定一个进程获取某种资源的先后顺序本质是进程获取cpu资源的优先顺序 为什么要有优先级 进程访问的资源&am…...
【Rust】语言特点介绍
Rust 教程 Rust 语言是一种高效、可靠的通用高级语言。其高效不仅限于开发效率,它的执行效率也是令人称赞的,是一种少有的兼顾开发效率和执行效率的语言。 Rust 语言由 Mozilla 开发,最早发布于 2014 年 9 月。Rust 的编译器是在 MIT Licens…...

接口冒烟测试方法
接口冒烟测试方法 今年遇到了几个问题,与接口的功能和性能相关,恰巧最近公司也在组织以冒烟测试为主题的活动,于是乎突发奇想,寻思着能否将接口测试与冒烟测试结合起来,发掘一些新的接口测试思路与方法。 平时对接口…...

Redis 全景图(3)--- Redis 应用于缓存
前言 这是关于 Redis 全景图的最后一篇文章。因为一次写太多会限流,我也是没办法,才分成三篇文章来写。这篇文章是关于 Redis 应用于缓存的。 其实为什么要讲这个话题呢? Redis 应用在很多地方呀,为什么一定要挑着这个话题来讲呢…...
vue中splice方法总结
本文没有目录,很简单的几句话总结一下 1,参数解释2,使用方法 splice(index,len,item)是vue中对数组进行操作的方法之一,可以用来 删除, 更新,和 增加数组内容。 1,参数解释 index:…...

【HTML】CSS样式(二)
上一篇我们学习了CSS基本样式和选择器,相信大家对于样式的使用有了初步认知。 本篇我们继续来学习CSS中的扩展选择器及CSS继承性,如何使用这些扩展选择器更好的帮助我们美化页面。 下一篇我们将会学习CSS中常用的属性。 喜欢的 【点赞】【关注】【收藏】…...

Java 学习和实践笔记(51):二分法查找(折半检索)
二分法查找(折半检索)又叫binary search. 要在一堆数据中查找是否存在某一个已知数,二分法查找的步骤: 第一步,对数据实现排序 第二步,将该数与排序后的数据集的中间一个数进行比较 第三步,…...

echarts 地图 自己圈地图 乡镇街道
这个是方式是我实在不愿意做的! 如果有现成的最好,没有办法的情况下再用这个东西。 今天公司有一个项目,地方划分了一块区域,但是国家没有审核,但是项目里面用到了一个地图展示数据!然后就需要我们自己把…...

12-1-CSS 常用样式属性
个人主页:学习前端的小z 个人专栏:HTML5和CSS3悦读 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结,欢迎大家在评论区交流讨论! 文章目录 CSS 常用样式属性1 CSS 三角形2 CSS 用户界面样式2.1 什么是界面样式2.2 鼠标…...

微信小程序短链接工具推荐
现在微信小程序大行其道,但工作中大部分人选择了短链接的方式来推广微信小程序,那么微信小程序短链接工具哪个好?今天就分享一篇从网上看到的关于《微信小程序短链接工具推荐》文,作者是souki,一起来看看吧! 一、缩链 1、生成方…...

[Spring Cloud] gateway全局异常捕捉统一返回值
文章目录 处理转发失败的情况全局参数同一返回格式操作消息对象AjaxResult返回值状态描述对象AjaxStatus返回值枚举接口层StatusCode 全局异常处理器自定义通用异常定一个自定义异常覆盖默认的异常处理自定义异常处理工具 在上一篇章时我们有了一个简单的gateway网关 [Spring C…...
网络基础二——TCP可靠性实现机制补充2
验证客户端和服务端三次握手和四次挥手时的状态 三次握手 #include <sys/types.h> #include <sys/socket.h> int listen(int sockfd, int backlog);netstat ntp //查看连接的状态 将TCP服务端套接字设置为listen状态之后,此时服务端是处于L…...

SSM项目实战——哈哈音乐(四)前台模块开发
1、项目准备 ①导入依赖和前端资源 <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.x…...

Hadoop-入门
资料来源:尚硅谷-Hadoop 一、Hadoop 概述 1.1 Hadoop 是什么 1)Hadoop是一个由Apache基金会所开发的分布式系统基础架构。 2)主要解决:海量数据的存储和海量数据的分析计算问题。 3)广义上来说,Hadoop…...

HarmonyOS(鸿蒙)——单击事件
2.4 实现ClickedListener接口并重写onClick方法 2.5 实现onClick方法中的具体逻辑,以此完成点击事件的相关业务操作 三、测试 3.1 登录远程模拟器 3.2 运行项目 四、精选好文 一、简介 1.1 什么是组件 组件就是文本、按钮、图片等元素的统称 1.2 什么是事件 …...
QMC5883L的驱动
简介 本篇文章的代码已经上传到了github上面,开源代码 作为一个电子罗盘模块,我们可以通过I2C从中获取偏航角yaw,相对于六轴陀螺仪的yaw,qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...
Neo4j 集群管理:原理、技术与最佳实践深度解析
Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...
LLM基础1_语言模型如何处理文本
基于GitHub项目:https://github.com/datawhalechina/llms-from-scratch-cn 工具介绍 tiktoken:OpenAI开发的专业"分词器" torch:Facebook开发的强力计算引擎,相当于超级计算器 理解词嵌入:给词语画"…...

微信小程序云开发平台MySQL的连接方式
注:微信小程序云开发平台指的是腾讯云开发 先给结论:微信小程序云开发平台的MySQL,无法通过获取数据库连接信息的方式进行连接,连接只能通过云开发的SDK连接,具体要参考官方文档: 为什么? 因为…...
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南 在数字化营销时代,邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天,我们将深入解析邮件打开率、网站可用性、页面参与时…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...

【分享】推荐一些办公小工具
1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由:大部分的转换软件需要收费,要么功能不齐全,而开会员又用不了几次浪费钱,借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...

Netty从入门到进阶(二)
二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架,用于…...
Vue 模板语句的数据来源
🧩 Vue 模板语句的数据来源:全方位解析 Vue 模板(<template> 部分)中的表达式、指令绑定(如 v-bind, v-on)和插值({{ }})都在一个特定的作用域内求值。这个作用域由当前 组件…...

java高级——高阶函数、如何定义一个函数式接口类似stream流的filter
java高级——高阶函数、stream流 前情提要文章介绍一、函数伊始1.1 合格的函数1.2 有形的函数2. 函数对象2.1 函数对象——行为参数化2.2 函数对象——延迟执行 二、 函数编程语法1. 函数对象表现形式1.1 Lambda表达式1.2 方法引用(Math::max) 2 函数接口…...