【C++】C++继承——切片、隐藏、默认成员函数、菱形
📝个人主页:@Sherry的成长之路
🏠学习社区:Sherry的成长之路(个人社区)
📖专栏链接:C++学习
🎯长路漫漫浩浩,万事皆有期待
上一篇博客:【C++】STL详解(八)—— priority_queue的使用及模拟实现&&仿函数
文章目录
- 继承的概念及定义
- 继承的概念
- 继承的定义
- 定义格式
- 继承方式和访问限定符
- 继承基类成员访问方式的变化
- 默认继承方式
- 基类和派生类对象赋值转换
- 继承中的作用域
- 派生类的默认成员函数
- 继承与友元
- 继承的方式
- 菱形虚拟继承
- 菱形虚拟继承原理
- 继承的总结和反思
- 相关笔试面试题
- 总结:
继承的概念及定义
继承的概念
继承(inheritance)机制是面向对象程序设计使代码可以复用的重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称为派生类。
继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,而继承便是类设计层次的复用。
例如,以下代码中Student类和Teacher类就继承了Person类。
//父类
class Person
{
public:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "张三"; //姓名int _age = 18; //年龄
};
//子类
class Student : public Person
{
protected:int _stuid; //学号
};
//子类
class Teacher : public Person
{
protected:int _jobid; //工号
};
继承后,父类Person的成员,包括成员函数和成员变量,都会变成子类的一部分,也就是说,子类Student和Teacher复用了父类Person的成员。
继承的定义
定义格式
继承的定义格式如下:
说明: 在继承当中,父类也称为基类,子类是由基类派生而来的,所以子类又称为派生类。
继承方式和访问限定符
我们知道,访问限定符有以下三种:
1.public访问
2.protected访问
3.private访问
而继承的方式也有类似的三种:
1.public继承
2.protected继承
3.private继承
继承基类成员访问方式的变化
基类当中被不同访问限定符修饰的成员,以不同的继承方式继承到派生类当中后,该成员最终在派生类当中的访问方式将会发生变化。
稍作观察,实际上基类成员访问方式的变化规则也不是无迹可寻的,我们可以认为三种访问限定符的权限大小为:public > protected > private,基类成员访问方式的变化规则如下:
1.在基类当中的访问方式为public或protected的成员,在派生类当中的访问方式变为:Min(成员在基类的访问方式,继承方式)。
2.在基类当中的访问方式为private的成员,在派生类当中都是不可见的。
基类的private成员在派生类当中不可见是什么意思?
这句话的意思是,我们无法在派生类当中访问基类的private成员。例如,虽然Student类继承了Person类,但是我们无法在Student类当中访问Person类当中的private成员_name。
//基类
class Person
{
private:string _name = "张三"; //姓名
};
//派生类
class Student : public Person
{
public:void Print(){//在派生类当中访问基类的private成员,error!cout << _name << endl; }
protected:int _stuid; //学号
};
也就是说,基类的private成员无论以什么方式继承,在派生类中都是不可见的,这里的不可见是指基类的私有成员虽然被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
因此,基类的private成员在派生类中是不能被访问的,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就需要定义为protected,由此可以看出,protected限定符是因继承才出现的。
注意
: 在实际运用中一般使用的都是public继承,几乎很少使用protected和private继承,也不提倡使用protected和private继承,因为使用protected和private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。
默认继承方式
在使用继承的时候也可以不指定继承方式,使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public。
例如,在关键字为class的派生类当中,所继承的基类成员_name的访问方式变为private。
//基类
class Person
{
public:string _name = "张三"; //姓名
};
//派生类
class Student : Person //默认为private继承
{
protected:int _stuid; //学号
};
而在关键字为struct的派生类当中,所继承的基类成员_name的访问方式仍为public。
//基类
class Person
{
public:string _name = "张三"; //姓名
};
//派生类
struct Student : Person //默认为public继承
{
protected:int _stuid; //学号
};
注意
: 虽然继承时可以不指定继承方式而采用默认的继承方式,但还是最好显示的写出继承方式。
基类和派生类对象赋值转换
派生类对象可以赋值给基类的对象、基类的指针以及基类的引用,因为在这个过程中,会发生基类和派生类对象之间的赋值转换。
例如,对于以下基类及其派生类。
//基类
class Person
{
protected:string _name; //姓名string _sex; //性别int _age; //年龄
};
//派生类
class Student : public Person
{
protected:int _stuid; //学号
};
代码当中可以出现以下逻辑:
Student s;
Person p = s; //派生类对象赋值给基类对象
Person* ptr = &s; //派生类对象赋值给基类指针
Person& ref = s; //派生类对象赋值给基类引用
对于这种做法,有个形象的说法叫做切片/切割,寓意把派生类中基类那部分切来赋值过去。
派生类对象赋值给基类对象图示:
派生类对象赋值给基类指针图示:
派生类对象赋值给基类引用图示:
注意: 基类对象不能赋值给派生类对象,基类的指针可以通过强制类型转换赋值给派生类的指针,但是此时基类的指针必须是指向派生类的对象才是安全的。
继承中的作用域
在继承体系中的基类和派生类都有独立的作用域。若子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。
例如,对于以下代码,访问成员_num时将访问到子类当中的_num。
#include <iostream>
#include <string>
using namespace std;
//父类
class Person
{
protected:int _num = 111;
};
//子类
class Student : public Person
{
public:void fun(){cout << _num << endl;}
protected:int _num = 999;
};
int main()
{Student s;s.fun(); //999return 0;
}
若此时我们就是要访问父类当中的_num成员,我们可以使用作用域限定符进行指定访问。
void fun()
{cout << Person::_num << endl; //指定访问父类当中的_num成员
}
需要注意的是,如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
例如,对于以下代码,调用成员函数fun时将直接调用子类当中的fun,若想调用父类当中的fun,则需使用作用域限定符指定类域。
#include <iostream>
#include <string>
using namespace std;
//父类
class Person
{
public:void fun(int x){cout << x << endl;}
};
//子类
class Student : public Person
{
public:void fun(double x){cout << x << endl;}
};
int main()
{Student s;s.fun(3.14); //直接调用子类当中的成员函数funs.Person::fun(20); //指定调用父类当中的成员函数funreturn 0;
}
特别注意
: 代码当中,父类中的fun和子类中的fun不是构成函数重载,因为函数重载要求两个函数在同一作用域,而此时这两个fun函数并不在同一作用域。为了避免类似问题,实际在继承体系当中最好不要定义同名的成员。因此为隐藏关系。
派生类的默认成员函数
默认成员函数,即我们不写编译器会自动生成的函数,类当中的默认成员函数有以下六个:
下面我们看看派生类当中的默认成员函数,与普通类的默认成员函数的不同之处。
例如,我们以下面这个Person类为基类。
//基类
class Person
{
public://构造函数Person(const string& name = "peter"):_name(name){cout << "Person()" << endl;}//拷贝构造函数Person(const Person& p):_name(p._name){cout << "Person(const Person& p)" << endl;}//赋值运算符重载函数Person& operator=(const Person& p){cout << "Person& operator=(const Person& p)" << endl;if (this != &p){_name = p._name;}return *this;}//析构函数~Person(){cout << "~Person()" << endl;}
private:string _name; //姓名
};
我们用该基类派生出Student类,Student类当中的默认成员函数的基本逻辑如下:
//派生类
class Student : public Person
{
public://构造函数Student(const string& name, int id):Person(name) //调用基类的构造函数初始化基类的那一部分成员, _id(id) //初始化派生类的成员{cout << "Student()" << endl;}//拷贝构造函数Student(const Student& s):Person(s) //调用基类的拷贝构造函数完成基类成员的拷贝构造, _id(s._id) //拷贝构造派生类的成员{cout << "Student(const Student& s)" << endl;}//赋值运算符重载函数Student& operator=(const Student& s){cout << "Student& operator=(const Student& s)" << endl;if (this != &s){Person::operator=(s); //调用基类的operator=完成基类成员的赋值_id = s._id; //完成派生类成员的赋值}return *this;}//析构函数~Student(){cout << "~Student()" << endl;//派生类的析构函数会在被调用完成后自动调用基类的析构函数}
private:int _id; //学号
};
派生类与普通类的默认成员函数的不同之处概括为以下几点:
派生类的构造函数被调用时,会自动调用基类的构造函数初始化基类的那一部分成员,如果基类当中没有默认的构造函数,则必须在派生类构造函数的初始化列表当中显示调用基类的构造函数。
派生类的拷贝构造函数必须调用基类的拷贝构造函数完成基类成员的拷贝构造。
派生类的赋值运算符重载函数必须调用基类的赋值运算符重载函数完成基类成员的赋值。
派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。
派生类对象初始化时,会先调用基类的构造函数再调用派生类的构造函数。
派生类对象在析构时,会先调用派生类的析构函数再调用基类的析构函数。
在编写派生类的默认成员函数时,需要注意以下几点:
派生类和基类的赋值运算符重载函数因为函数名相同构成隐藏,因此在派生类当中调用基类的赋值运算符重载函数时,需要使用作用域限定符进行指定调用。
由于多态的某些原因,任何类的析构函数名都会被统一处理为destructor();。因此,派生类和基类的析构函数也会因为函数名相同构成隐藏,若是我们需要在某处调用基类的析构函数,那么就要使用作用域限定符进行指定调用。
在派生类的拷贝构造函数和operator=当中调用基类的拷贝构造函数和operator=的传参方式是一个切片行为,都是将派生类对象直接赋值给基类的引用。
说明一下:
基类的构造函数、拷贝构造函数、赋值运算符重载函数我们都可以在派生类当中自行进行调用,而基类的析构函数是当派生类的析构函数被调用后由编译器自动调用的,我们若是自行调用基类的构造函数就会导致基类被析构多次的问题。
我们知道,创建派生类对象时是先创建的基类成员再创建的派生类成员,编译器为了保证析构时先析构派生类成员再析构基类成员的顺序析构,所以编译器会在派生类的析构函数被调用后自动调用基类的析构函数。
继承与友元
友元关系不能继承,也就是说基类的友元可以访问基类的私有和保护成员,但是不能访问派生类的私有和保护成员。
例如,以下代码中Display函数是基类Person的友元,当时Display函数不是派生类Student的友元,即Display函数无法访问派生类Student当中的私有和保护成员。
#include <iostream>
#include <string>
using namespace std;
class Student;
class Person
{
public://声明Display是Person的友元friend void Display(const Person& p, const Student& s);
protected:string _name; //姓名
};
class Student : public Person
{
protected:int _id; //学号
};
void Display(const Person& p, const Student& s)
{cout << p._name << endl; //可以访问cout << s._id << endl; //无法访问
}
int main()
{Person p;Student s;Display(p, s);return 0;
}
此时我们也可以通过打印Person类和Student类当中静态成员_count的地址来证明它们就是同一个变量。
cout << &Person::_count << endl; //00F1F320
cout << &Student::_count << endl; //00F1F320
比如下面这道题:
分析如下:
所以C正确
继承的方式
单继承:一个子类只有一个直接父类时称这个继承关系为单继承。
多继承:一个子类有两个或两个以上直接父类时称这个继承关系为多继承。
菱形继承:菱形继承是多继承的一种特殊情况。
从菱形继承的模型构造就可以看出,菱形继承的继承方式存在数据冗余和二义性的问题。
例如,对于以上菱形继承的模型,当我们实例化出一个Assistant对象后,访问成员时就会出现二义性问题。
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:string _name; //姓名
};
class Student : public Person
{
protected:int _num; //学号
};
class Teacher : public Person
{
protected:int _id; //职工编号
};
class Assistant : public Student, public Teacher
{
protected:string _majorCourse; //主修课程
};
int main()
{Assistant a;a._name = "peter"; //二义性:无法明确知道要访问哪一个_namereturn 0;
}
Assistant对象是多继承的Student和Teacher,而Student和Teacher当中都继承了Person,因此Student和Teacher当中都有_name成员,若是直接访问Assistant对象的_name成员会出现访问不明确的报错。
对于此,我们可以显示指定访问Assistant哪个父类的_name成员。
//显示指定访问哪个父类的成员
a.Student::_name = "张同学";
a.Teacher::_name = "张老师";
虽然该方法可以解决二义性的问题,但仍然不能解决数据冗余的问题。因为在Assistant的对象在Person成员始终会存在两份。
菱形虚拟继承
为了解决菱形继承的二义性和数据冗余问题,出现了虚拟继承。如前面说到的菱形继承关系,在Student和Teacher继承Person是使用虚拟继承,即可解决问题。
虚拟继承代码如下:
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:string _name; //姓名
};
class Student : virtual public Person //虚拟继承
{
protected:int _num; //学号
};
class Teacher : virtual public Person //虚拟继承
{
protected:int _id; //职工编号
};
class Assistant : public Student, public Teacher
{
protected:string _majorCourse; //主修课程
};
int main()
{Assistant a;a._name = "peter"; //无二义性return 0;
}
此时就可以直接访问Assistant对象的_name成员了,并且之后就算我们指定访问Assistant的Student父类和Teacher父类的_name成员,访问到的都是同一个结果,解决了二义性的问题。
cout << a.Student::_name << endl; //peter
cout << a.Teacher::_name << endl; //peter
而我们打印Assistant的Student父类和Teacher父类的_name成员的地址时,显示的也是同一个地址,解决了数据冗余的问题。
cout << &a.Student::_name << endl; //0136F74C
cout << &a.Teacher::_name << endl; //0136F74C
菱形虚拟继承原理
在此之前,我们先看看不使用菱形虚拟继承时,以下菱形继承当中D类对象的各个成员在内存当中的分布情况。
测试代码如下:
#include <iostream>
using namespace std;
class A
{
public:int _a;
};
class B : public A
{
public:int _b;
};
class C : public A
{
public:int _c;
};
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;
}
通过内存窗口,我们可以看到D类对象当中各个成员在内存当中的分布情况如下:
也就是说,D类对象当中各个成员在内存当中的分布情况如下:
这里就可以看出为什么菱形继承导致了数据冗余和二义性,根本原因就是D类对象当中含有两个_a成员。
现在我们再来看看使用菱形虚拟继承时,以下菱形继承当中D类对象的各个成员在内存当中的分布情况。
测试代码如下:
#include <iostream>
using namespace std;
class A
{
public:int _a;
};
class B : virtual public A
{
public:int _b;
};
class C : virtual public A
{
public:int _c;
};
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;
}
通过内存窗口,我们可以看到D类对象当中各个成员在内存当中的分布情况如下:
其中D类对象当中的_a成员被放到了最后,而在原来存放两个_a成员的位置变成了两个指针,这两个指针叫虚基表指针,它们分别指向一个虚基表。
虚基表中包含两个数据,第一个数据是为多态的虚表预留的存偏移量的位置(这里我们不必关心),第二个数据就是当前类对象位置距离公共虚基类的偏移量。
也就是说,这两个指针经过一系列的计算,最终都可以找到成员_a。
我们若是将D类对象赋值给B类对象,在这个切片过程中,就需要通过虚基表中的第二个数据找到公共虚基类A的成员,得到切片后该B类对象在内存中仍然保持这种分布情况。
D d;
B b = d; //切片行为
得到切片后该B类对象当中各个成员在内存当中的分布情况如下:
其中,_a对象仍然存储在该B类对象的最后。
继承的总结和反思
1.很多人都说C++语法复杂,其实多继承就是一个体现。有了多继承,就可能存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出菱形继承,否则代码在复杂度及性能上都容易出现问题,当菱形继承出问题时难以分析,并且会有一定的效率影响。
2.多继承可以认为是C++的缺陷之一,很多后来的OO(Object Oriented)语言都没有多继承,如Java。
继承和组合
继承是一种is-a的关系,也就是说每个派生类对象都是一个基类对象;而组合是一种has-a的关系,若是B组合了A,那么每个B对象中都有一个A对象。
例如,车类和宝马类就是is-a的关系,它们之间适合使用继承。
class Car
{
protected:string _colour; //颜色string _num; //车牌号
};
class BMW : public Car
{
public:void Drive(){cout << "this is BMW" << endl;}
};
而车和轮胎之间就是has-a的关系,它们之间则适合使用组合。
class Tire
{
protected:string _brand; //品牌size_t _size; //尺寸
};
class Car
{
protected:string _colour; //颜色string _num; //车牌号Tire _t; //轮胎
};
若是两个类之间既可以看作is-a的关系,又可以看作has-a的关系,则优先使用组合。
原因如下:
继承允许你根据基类的实现来定义派生类的实现,这种通过生成派生类的复用通常被称为白箱复用(White-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对于派生类可见,继承一定程度破坏了基类的封装,基类的改变对派生类有很大的影响,派生类和基类间的依赖性关系很强,耦合度高。
组合是类继承之外的另一种复用选择,新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口,这种复用风格被称之为黑箱复用(Black-box reuse),因为对象的内部细节是不可见的,对象只以“黑箱”的形式出现,组合类之间没有很强的依赖关系,耦合度低,优先使用对象组合有助于你保持每个类被封装。
实际中尽量多使用组合,组合的耦合度低,代码维护性好。不过继承也是有用武之地的,有些关系就适合用继承,另外要实现多态也必须要继承。若是类之间的关系既可以用继承,又可以用组合,则优先使用组合。
相关笔试面试题
什么是菱形继承?菱形继承的问题是什么?
菱形继承是多继承的一种特殊情况,两个子类继承同一个父类,而又有子类同时继承这两个子类,我们称这种继承为菱形继承。
菱形继承因为子类对象当中会有两份父类的成员,因此会导致数据冗余和二义性的问题。
什么是菱形虚拟继承?如何解决数据冗余和二义性?
菱形虚拟继承是指在菱形继承的腰部使用虚拟继承(virtual)的继承方式,菱形虚拟继承对于D类对象当中重复的A类成员只存储一份,然后采用虚基表指针和虚基表使得D类对象当中继承的B类和C类可以找到自己继承的A类成员,从而解决了数据冗余和二义性的问题。
继承和组合的区别?什么时候用继承?什么时候用组合?
继承是一种is-a的关系,而组合是一种has-a的关系。如果两个类之间是is-a的关系,使用继承;如果两个类之间是has-a的关系,则使用组合;如果两个类之间的关系既可以看作is-a的关系,又可以看作has-a的关系,则优先使用组合。
总结:
今天我们学习了C++继承中切片、隐藏、默认成员函数、菱形的相关知识,了解了一些有关的底层原理。接下来,我们将进行C++中多态的学习。希望我的文章和讲解能对大家的学习提供一些帮助。
当然,本文仍有许多不足之处,欢迎各位小伙伴们随时私信交流、批评指正!我们下期见~
相关文章:

【C++】C++继承——切片、隐藏、默认成员函数、菱形
📝个人主页:Sherry的成长之路 🏠学习社区:Sherry的成长之路(个人社区) 📖专栏链接:C学习 🎯长路漫漫浩浩,万事皆有期待 上一篇博客:【C】STL…...
WebGL笔记:WebGL中绘制圆点,设定透明度,渲染动画
WebGL 绘制圆点 基于片元着色器来画圆形片元着色器在屏幕中画图是基于一个个的像素的每次画一个像素时,都会执行片元着色器中的main方法那么,我们就可以从这一堆片元中(n个像素点)找出属于圆形的部分片元的位置叫做 gl_PointCoord (一个点中片元的坐标位…...

华为云云耀云服务器L实例评测 | 实例使用教学之简单使用:通过命令行管理华为云云耀云服务器
华为云云耀云服务器L实例评测 | 实例使用教学之简单使用:通过命令行管理华为云云耀云服务器 介绍华为云云耀云服务器 华为云云耀云服务器 (目前已经全新升级为 华为云云耀云服务器L实例) 华为云云耀云服务器是什么华为云云耀云服务…...

微信小程序 课程签到系统
目录 前端页面展示主页面我的课程个人中心评论功能签到功能课程绑定超级管理员页面 前端文件结构文件结构app.json前端架构和开发工具前端项目地址 后端后端架构后端项目地址 注意事项 前端页面展示 主页面 登录页面: 账号是:用户名或者手机号 密码是&a…...

如何用Postman做接口自动化测试
前言 什么是自动化测试 把人对软件的测试行为转化为由机器执行测试行为的一种实践。 例如GUI自动化测试,模拟人去操作软件界面,把人从简单重复的劳动中解放出来。 本质是用代码去测试另一段代码,属于一种软件开发工作,已经开发完成…...

支付宝电脑网站支付,异步通知
一:异步通知是支付宝回调商户的服务器,所以这个地址需要通过外网访问,在真实项目中都会有对应的服务器,但是在测试中只有使用内网穿透工具 推荐使用NATAPP-内网穿透 基于ngrok的国内高速内网映射工具 配置好内网穿透之后不要忘记…...

【广州华锐互动】奶牛养殖难产助产3D沉浸式教学平台
在传统的奶牛难产助产教学中,主要依赖理论知识和2D图像来进行教学。然而,这种教学方式往往无法全面、真实地展示奶牛难产的各种情况,教学效果也不尽如人意。随着科技的发展,3D互动教学的出现,为奶牛难产助产教学带来了…...

IDEA社区版,真香!
IDEA(IntelliJ IDEA)是众多 Java 开发者的首选。 商业版的昂贵 IDEA 商业版(IntelliJ IDEA Ultimate)功能非常强大,能够满足 Java 开发的所有需求,但其高昂的价格…… 此时只能感叹,不是不想用…...

SpringBoot实现全局异常处理
1.全局异常处理介绍 1.1 简介 全局异常处理器即把错误异常统一处理的方法,可以在多个地方使用,而不需要为每个地方编写单独的处理逻辑。它可以帮助开发人员更好地管理异常,并提供一致的错误处理方式。 1.2 优点 1.全局异常处理可以提高代码…...

Day05-循环高级和数组
循环高级 1.无限循环 概念: 又叫死循环。循环一直停不下来。 for格式: for(;;){System.out.println("循环执行一直在打印内容"); } 解释: 初始化语句可以空着不写,表示循环之前不定义任何的控制变量。 条件判断…...
从代码操作层面解释什么是“面相对象编程”?
起因: 今天开了一个小会,会上朋友给我们说了一个事,Java项目上他开发一个小功能 用了很多代码,项目经理发现代码太多,说要优化一下,然后亲自帮同事优化,结果是查库的代码少了至少10条sql&#x…...
【MySQL】SQL优化、char、varchar、外键约束、排查慢sql等重点知识汇总
目录 SQL语句 char和varchar比较 SQL语句如何优化 说一下你理解的外键约束 如何排查慢 sql SQL语句 对库操作 创建数据库 create database 数据库名 删除数据库 drop database 数据库名 显示所有数据库 show databases 选中数据库 use 数据库名 对表操作 创建表…...
git管理常用命令
1、下载代码 git clone 地址2、软件代码提交 1、查看工程中被修改的文件:git status 2.将不需要提交的文件回退:git check <文件路径> 3.更新工程到最新:git pull 4.将本地代码添加到暂存区:git add <将要提交的文件路…...

Python 逢七拍手小游戏2.0
"""逢七拍手游戏介绍:逢七拍手游戏的规则是:从1开始顺序数数,数到有7,或者是7的倍数时,就拍一手。例如:7、14、17......70......知识点:1、循环语句for2、嵌套条件语句if/elif/e…...

基于微信小程序的在线小说阅读系统,附数据库、教程
1 功能简介 Java基于微信小程序的在线小说阅读系统 微信小程序的在线小说阅读系统,系统的整体功能需求分为两部分,第一部分主要是后台的功能,后台功能主要有小说信息管理、注册用户管理、系统系统等功能。微信小程序主要分为首页、分类和我的…...
216. 组合总和 III
找出所有相加之和为 n 的 k 个数的组合,且满足下列条件: 只使用数字1到9每个数字 最多使用一次 返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。 示例 1: 输入: k 3, n 7 输出: [[1,2,4]] 解释: …...
【Java】数组的深浅拷贝问题(二维数组举例)(136)
深拷贝和浅拷贝: 对于数组来说,深拷贝就是相当于拷贝了数组的对象(基本数据类型),也就是数组当中的内容。而浅拷贝就是拷贝的是数组的地址(引用类型),浅拷贝只是复制了对象的引用地…...

【轮趣-科大讯飞】M260C 环形六麦测试 2 - ROS1功能测试与唤醒、语音识别程序解析
所有内容请看: 博客学习目录_Howe_xixi的博客-CSDN博客https://blog.csdn.net/weixin_44362628/article/details/126020573?spm1001.2014.3001.5502原文在飞书,请联系我获取阅读链接,我太懒了...

油猴(篡改猴)学习记录
第一个Hello World 注意点:默认只匹配了http网站,如果需要https网站,需要自己添加match https://*/*代码如下 这样子访问任意网站就可以输出Hello World // UserScript // name 第一个脚本 // namespace http://tampermonkey.net/ // version 0.1 // descri…...
LeetCode 面试题 05.08. 绘制直线
文章目录 一、题目二、Java 题解 一、题目 已知一个由像素点组成的单色屏幕,每行均有 w 个像素点,所有像素点初始为 0,左上角位置为 (0,0)。 现将每行的像素点按照「每 32 个像素点」为一组存放在一个 int 中,再依次存入长度为 le…...

Day131 | 灵神 | 回溯算法 | 子集型 子集
Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...
线程与协程
1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指:像函数调用/返回一样轻量地完成任务切换。 举例说明: 当你在程序中写一个函数调用: funcA() 然后 funcA 执行完后返回&…...

Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...

UDP(Echoserver)
网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法:netstat [选项] 功能:查看网络状态 常用选项: n 拒绝显示别名&#…...
React Native在HarmonyOS 5.0阅读类应用开发中的实践
一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强,React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 (1)使用React Native…...

定时器任务——若依源码分析
分析util包下面的工具类schedule utils: ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类,封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz,先构建任务的 JobD…...

学校招生小程序源码介绍
基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码,专为学校招生场景量身打造,功能实用且操作便捷。 从技术架构来看,ThinkPHP提供稳定可靠的后台服务,FastAdmin加速开发流程,UniApp则保障小程序在多端有良好的兼…...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...

CMake 从 GitHub 下载第三方库并使用
有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...

【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习
禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...