【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…...

大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...

【JavaEE】-- HTTP
1. HTTP是什么? HTTP(全称为"超文本传输协议")是一种应用非常广泛的应用层协议,HTTP是基于TCP协议的一种应用层协议。 应用层协议:是计算机网络协议栈中最高层的协议,它定义了运行在不同主机上…...

VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...

C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...

初学 pytest 记录
安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...

ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...