【C++】多态详解
💗个人主页💗
⭐个人专栏——C++学习⭐
💫点击关注🤩一起学习C语言💯💫
目录
一、多态概念
二、多态的定义及实现
1. 多态的构成条件
2. 虚函数
2.1 什么是虚函数
2.2 虚函数的重写
2.3 虚函数重写的两个例外
2.3.1 协变
2.3.2 析构函数的重写
3. override 和 final
4. 重载、覆盖(重写)、隐藏(重定义)的对比
三、抽象类
1. 概念
2. 接口继承和实现继承
四、多态的原理
1. 虚函数表
2. 多态的原理
3. 动态绑定与静态绑定
五、单继承和多继承关系的虚函数表
1. 单继承中的虚函数表
2. 多继承中的虚函数表
一、多态概念
多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会 产生出不同的状态。
举个栗子:比如买票这个行为,当普通人买票时,是全价买票;学生买票时,是半价买票;军人 买票时是优先买票。
多态的优势在于它增加了代码的灵活性和可扩展性。通过多态,我们可以编写通用的代码,处理多种类型的对象,使得程序更易于理解、扩展和维护。
二、多态的定义及实现
1. 多态的构成条件
那么在继承中要构成多态还有两个条件:
- 必须通过基类的指针或者引用调用虚函数
- 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。比如Student继承了 Person。Person对象买票全价,Student对象买票半价。
class Person
{
public:virtual void BuyTicket() {cout << "买票全价" << endl; }
};class Student : public Person
{
public:virtual void BuyTicket() { cout << "买票半价" << endl; }
};
void Func(Person& people)
{people.BuyTicket();
}
int main()
{Person Mike;Func(Mike);Student Johnson;Func(Johnson);return 0;
}
2. 虚函数
2.1 什么是虚函数
虚函数(virtual function)是面向对象编程中的一个概念,它是用来实现多态的机制之一。
虚函数:即被virtual修饰的类成员函数称为虚函数。
虚函数是在基类中声明的一种特殊的成员函数,用关键字virtual进行声明。当在派生类中重写(override)这个函数时,可以使用关键字override来显式标识。
通过将函数声明为虚函数,可以实现动态绑定(dynamic binding),即在运行时根据对象的实际类型来调用相应的函数。这意味着当通过基类指针或引用调用虚函数时,实际调用的是对应的派生类中的函数。
class Person
{
public:virtual void BuyTicket() { cout << "买票全价" << endl; }
};
2.2 虚函数的重写
虚函数的重写是指在派生类中重新定义(override)基类中已经声明的虚函数。通过重写虚函数,可以在派生类中提供自己的实现,以替代基类中的默认实现。
重写虚函数的规则如下:
- 基类中的虚函数必须使用关键字virtual进行声明。
- 派生类中的重写函数必须具有相同的名称、参数列表和返回类型。
- 派生类中的重写函数必须使用关键字override进行标识,以确保编译器进行正确的检查。
- 重写函数可以被声明为虚函数,但这不是必需的。
class Animal
{
public:virtual void makeSound() {cout << "Animal is making a sound" << endl;}
};class Dog : public Animal
{
public:void makeSound() override { // 重写虚函数cout << "Dog is barking" << endl;}
};class Cat : public Animal
{
public:void makeSound() override { // 重写虚函数cout << "Cat is meowing" << endl;}
};int main()
{Animal* animal = new Animal();Animal* dog = new Dog();Animal* cat = new Cat();animal->makeSound(); // 输出 "Animal is making a sound"dog->makeSound(); // 输出 "Dog is barking"cat->makeSound(); // 输出 "Cat is meowing"delete animal;delete dog;delete cat;return 0;
}
在上述代码中,Animal类中的makeSound()函数被声明为虚函数。派生类Dog和Cat分别重写了这个函数,并提供了自己的实现。
在main()函数中,通过基类指针调用makeSound()函数时,实际上会根据指针指向的对象类型来调用相应的派生类中的函数。这就是虚函数的重写实现多态性的一种方式。
2.3 虚函数重写的两个例外
2.3.1 协变
虚函数的协变(covariant)是指派生类可以返回比基类更具体的类型。
换句话说,派生类可以重写基类中的虚函数,并返回一个派生类类型的指针或引用,而不仅仅是基类类型的指针或引用。
协变发生在满足以下条件时:
- 基类中的虚函数必须返回指针或引用类型。
- 派生类中重写的函数可以返回指向派生类的指针或引用类型,该派生类是基类中返回类型的派生类。
class Animal
{
public:virtual Animal* create() {return new Animal();}
};class Dog : public Animal
{
public:Dog* create() override { // 协变的重写函数return new Dog();}
};class Cat : public Animal
{
public:Cat* create() override { // 协变的重写函数return new Cat();}
};int main()
{Animal* animal = new Animal();Animal* dog = new Dog();Animal* cat = new Cat();Animal* newAnimal = animal->create();Animal* newDog = dog->create();Animal* newCat = cat->create();delete animal;delete dog;delete cat;delete newAnimal;delete newDog;delete newCat;return 0;
}
在上述代码中,Animal类中的create()函数返回一个指向Animal类型的指针。派生类Dog和Cat分别重写了这个函数,并返回指向Dog和Cat类型的指针。
在main()函数中,通过调用基类指针的create()函数,实际上会调用对应派生类中重写的create()函数。由于协变的存在,可以将返回的指针赋值给指向基类的指针,这样便保留了派生类的类型信息。
注意,为了实现协变,返回类型必须是指针或引用,而不能是值类型。否则无法满足派生类返回类型更具体的要求。
2.3.2 析构函数的重写
如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字, 都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同, 看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor。
以下是一个示例,展示了虚函数中的析构函数重写是没有意义的:
class Animal
{
public:Animal() {cout << "Animal constructor called." << endl;}virtual ~Animal() {cout << "Animal destructor called." << endl;}virtual void speak() {cout << "Animal speaks." << endl;}
};class Dog : public Animal
{
public:Dog() {cout << "Dog constructor called." << endl;}~Dog() {cout << "Dog destructor called." << endl;}void speak() override {cout << "Dog barks." << endl;}
};int main()
{Animal* animal = new Dog();animal->speak();delete animal;return 0;
}
Animal类定义了一个虚函数speak(),而派生类Dog中重写了该函数。在main()函数中,通过基类指针创建了一个Dog对象,然后调用speak()函数。
从结果可以看出,析构函数按照预期顺序调用,无需在派生类中显式重写。同时,虚函数speak()也可以正确地根据对象的实际类型调用对应的版本。
3. override 和 final
从上面可以看出,C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有 得到预期结果才来debug会得不偿失,因此:C++11提供了override和final两个关键字,可以帮助用户检测是否重写。
1. final:修饰虚函数,表示该虚函数不能再被重写
class Base
{
public:virtual void foo() final {// 函数实现}
};class Derived : public Base
{
public:void foo() override { // 编译错误,不能重写被声明为final的虚函数// 函数实现}
};
2. override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。
class Base
{
public:virtual void foo() {// 函数实现}
};class Derived : public Base
{
public:void foo() override { // 使用override关键字重写基类的虚函数// 函数实现}
};
4. 重载、覆盖(重写)、隐藏(重定义)的对比
三、抽象类
1. 概念
在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口 类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生 类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。
抽象类可以用于实现接口的一致性和多态性。通过基类指针或引用,可以在运行时动态地选择调用派生类的实现。
class Animal
{
public:virtual void makeSound() = 0; // 纯虚函数void sleep() {cout << "Zzz..." << endl;}
};class Dog : public Animal
{
public:void makeSound() override {cout << "Woof!" << endl;}
};class Cat : public Animal {
public:void makeSound() override {cout << "Meow!" << endl;}
};int main()
{Animal* animalPtr = new Dog();animalPtr->makeSound(); // 输出 "Woof!"animalPtr->sleep(); // 输出 "Zzz..."delete animalPtr;animalPtr = new Cat();animalPtr->makeSound(); // 输出 "Meow!"animalPtr->sleep(); // 输出 "Zzz..."delete animalPtr;return 0;
}
Animal
是一个抽象类,包含一个纯虚函数makeSound()
和一个普通的成员函数sleep()
。Dog
和Cat
是Animal
的派生类,必须实现makeSound()
函数。通过基类指针,可以调用派生类的实现。
需要注意的是,无法直接实例化抽象类,因为它包含纯虚函数没有具体的实现。派生类必须实现所有纯虚函数,才能被实例化。抽象类可以作为基类来定义其他类,从而实现代码的复用和扩展。
2. 接口继承和实现继承
接口继承和实现继承是面向对象编程中的两种继承方式。
-
接口继承(Interface Inheritance)是指一个接口(interface)可以继承自另一个接口,继承接口的子接口会继承父接口的所有方法定义。接口继承主要用于定义一组共享的方法规范,子接口可以继续扩展这些方法规范,同时可以添加自己的方法规范。接口继承可以实现多继承的效果,一个类可以同时实现多个接口。
-
实现继承(Implementation Inheritance)是指一个类(class)可以继承自另一个类,继承类会继承父类的属性和方法。实现继承主要用于类之间的层级关系,子类可以继承父类的行为和状态,同时还可以添加自己特有的行为和状态。实现继承是一种单继承的方式,一个类只能直接继承一个父类。
普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。
四、多态的原理
1. 虚函数表
我们来看下面的代码,想一下sizeof(a)是多少。
class A
{
public:virtual void Func1(){cout << "Func1()" << endl;}
private:int _b = 1;
};
int main()
{A a;cout << sizeof(a) << endl;return 0;
}
第一眼看去,这不就是4吗,我们来看一下运行结果:
为什么呢?我们打开监视窗口来看一下:
我们发现,a里面不仅仅存在一个_b,还有一个_vfptr放在对象的前面(注意有些 平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)。
一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数 的地址要被放到虚函数表中,虚函数表也简称虚表。
那么派生类中这个表放了些什么呢?我们接着往下分析,针对上面的代码我们做出以下改造
- 我们增加一个派生类Derive去继承Base
- Derive中重写Func1
- Base再增加一个虚函数Func2和一个普通函数Func3
class A
{
public:virtual void Func1(){cout << "A::Func1()" << endl;}virtual void Func2(){cout << "A::Func2()" << endl;}void Func3(){cout << "A::Func3()" << endl;}
private:int _a = 1;
};
class B : public A
{
public:virtual void Func1(){cout << "Derive::Func1()" << endl;}
private:int _b = 2;
};
int main()
{A a;B b;cout << sizeof(a) << endl;cout << sizeof(b) << endl;return 0;
}
我们再来看下这个代码结果是多少呢:
这又是为什么呢?我们继续来看一下调试窗口:
通过观察和测试,我们发现了以下几点问题:
- 派生类对象b中也有一个虚表指针,b对象由两部分构成,一部分是父类继承下来的成员,虚表指针也就是存在部分的另一部分是自己的成员。
- 基类a对象和派生类b对象虚表是不一样的,这里我们发现Func1完成了重写,所以b的虚表中存的是重写的B::Func1,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法。
- 另外Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函 数,所以不会放进虚表。
- 虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr。
总结一下派生类的虚表生成:
- 先将基类中的虚表内容拷贝一份到派生类虚表中
- 如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数
- 派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。
这里还有一个童鞋们很容易混淆的问题:虚函数存在哪的?虚表存在哪的?
答:虚函数存在虚表,虚表存在对象中。注意上面的回答的错的。但是很多童鞋都是这样深以为然的。
注意虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针。那么虚表存在哪的呢?实际我们去验证一下会发现vs下是存在代码段的。
2. 多态的原理
上面分析了这个半天了那么多态的原理到底是什么?我们来继续探索。
class A
{
public:virtual void Func1(){cout << "A::Func1()" << endl;}
private:int _a = 1;
};
class B : public A
{
public:virtual void Func1(){cout << "Derive::Func1()" << endl;}
private:int _b = 2;
};
int main()
{A a;B b;return 0;
}
我们会疑惑,为什么多态可以实现指向父类调用父类函数 ,指向子类调用子类函数?
我们来看看虚表里的指针内容:
在监视窗口里我们可以看到a对象和b对象里的内容,我们继续打开内存窗口,取地址a,
我们看到第一个内容就是_vfptr的地址,继续通过_vfptr的地址,我们就能看到虚表里的内容,这时我们又看到了虚函数的地址。
我们来取地址b,发现和上诉同理。
但是,这时我们发现怎么在B类里的A的虚函数地址,和它原本在A类里的地址不一样,这就是虚函数的重写覆盖。
当一个类声明了虚函数时,编译器会为该类生成一个虚函数表。虚函数表是一个存储了虚函数指针的表格,每个包含虚函数的类都会有自己的虚函数表。虚函数表的每个表项存储了相应虚函数的地址。对于派生类,虚函数表中的表项可能会被重写以指向派生类中的虚函数。
当使用基类指针或引用调用虚函数时,编译器会根据对象的实际类型来查找虚函数表,并调用相应的虚函数。这种通过虚函数表来实现动态绑定的机制使得C++中的对象能够在运行时根据实际类型来确定调用的函数,而不是在编译时就确定下来。
3. 动态绑定与静态绑定
动态绑定和静态绑定是两种不同的函数调用机制,它们在编程语言中起到的作用也不同。
- 静态绑定(静态分派): 静态绑定是在编译时期完成的,也称为早期绑定。在静态绑定中,函数调用的目标函数是在编译时根据对象的静态类型确定的。当调用一个函数时,编译器会根据对象的声明类型来确定要调用的函数,而不管对象的实际类型。
静态绑定适用于非虚函数和静态函数,这些函数的调用目标不会发生改变。在静态绑定中,对象的方法调用是基于声明类型进行的,也就是编译期间根据对象的静态类型来确定调用哪个函数,无法体现多态性。
- 动态绑定(动态分派): 动态绑定是在运行时期完成的,也称为晚期绑定。在动态绑定中,函数调用的目标函数是根据对象的实际类型来确定的。当调用一个虚函数时,编译器会根据对象的实际类型来确定要调用的函数。
动态绑定适用于虚函数,虚函数表(vtable)是实现动态绑定的一种机制。每个包含虚函数的类都会有一个虚函数表,该表存储了类中虚函数的地址。运行时系统通过指向基类的指针或引用,查找对象实际的类型,并根据其虚函数表来确定要调用的函数。
动态绑定允许程序在运行时根据对象的实际类型来选择调用的函数,实现了多态性。这样可以实现基于对象的具体行为,并提高代码的灵活性。
五、单继承和多继承关系的虚函数表
1. 单继承中的虚函数表
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;
};
int main()
{Base b;Derive d;return 0;
}
观察下图中的监视窗口中我们发现看不见func3和func4。这里是编译器的监视窗口故意隐藏了这 两个函数,也可以认为是他的一个小bug。那么我们如何查看d的虚表呢?下面我们使用代码打印 出虚表中的函数。
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;
};
typedef void(*VFTPTR)();void PrintVFPtr(VFTPTR a[])
{for (size_t i = 0; a[i] != 0; i++){printf("a[%d]:%p\n", i, a[i]);}cout << endl;
}int main()
{Base b;Derive d;PrintVFPtr((VFTPTR*)*(int*)&b);PrintVFPtr((VFTPTR*)*(int*)&d);return 0;
}
2. 多继承中的虚函数表
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()
{Derive d;VFPTR* vTableb1 = (VFPTR*)(*(int*)&d);PrintVTable(vTableb1);VFPTR* vTableb2 = (VFPTR*)(*(int*)((char*)&d + sizeof(Base1)));PrintVTable(vTableb2);return 0;
}
观察下图可以看出:多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中
相关文章:

【C++】多态详解
💗个人主页💗 ⭐个人专栏——C学习⭐ 💫点击关注🤩一起学习C语言💯💫 目录 一、多态概念 二、多态的定义及实现 1. 多态的构成条件 2. 虚函数 2.1 什么是虚函数 2.2 虚函数的重写 2.3 虚函数重写的两个…...

C#异常捕获
前言 在C#中,我们无法保证我们编写的程序没有一点bug,如果我们对于这些抛出异常的bug不进行任何的处理的话,那么我们的软件在抛出这些异常的时候就会崩溃,也就是软件闪退,并且这种闪退由于我们没有进行处理࿰…...

工业一体机根据软件应用需求灵活选配
在当今工业领域,数字化、智能化的发展趋势愈发明显,工业一体机作为关键的设备,其重要性日益凸显。而能够根据软件应用需求进行灵活选配的工业一体机,更是为企业提供了高效、定制化的解决方案。 一、工业一体机的全封闭无风扇散热功…...

centos7 mqtt服务mosquitto搭建记录
1、系统centos7.6,安装默认版本 yum install mosquitto 2、启动运行 systemctl start mosquitto 3、设置自启动 systemctl enable mosquitto 4、修改配置文件 vim /etc/mosquitto/mosquitto.conf 监听端口,默认为1883,需要修改删除前面…...

双阶段目标检测算法:精确与效率的博弈
双阶段目标检测算法:精确与效率的博弈 目标检测是计算机视觉领域的一个核心任务,它涉及在图像或视频中识别和定位多个对象。双阶段目标检测算法是一种特殊的目标检测方法,它通过两个阶段来提高检测的准确性。本文将详细介绍双阶段目标检测算…...

Python量化交易策略
策略详情 按照1分k线图;跳过9:30点1分k线图不计算 买入;监控市面的可转债;当某1分涨幅大于x涨幅,一直重复x次,选择买入,符合x设置的条件只选择成交额最大的可转债买入(x要自定义&…...

为什么我感觉 C 语言在 Linux 下执行效率比 Windows 快得多?
在开始前刚好我有一些资料,是我根据网友给的问题精心整理了一份「Linux的资料从专业入门到高级教程」, 点个关注在评论区回复“888”之后私信回复“888”,全部无偿共享给大家!!!Windows的终端或者叫控制台…...

算法导论 总结索引 | 第四部分 第十六章:贪心算法
1、求解最优化问题的算法 通常需要经过一系列的步骤,在每个步骤都面临多种选择。对于许多最优化问题,使用动态规划算法求最优解有些杀鸡用牛刀了,可以使用更简单、更高效的算法 贪心算法(greedy algorithm)就是这样的算…...

用“文心一言”写的文章,看看AI写得怎么样?
零售连锁店的“支付结算”业务设计 在数字化浪潮的推动下,连锁店零售支付结算的设计愈发重要。一个优秀的支付结算设计不仅能够提升用户体验,还能增强品牌竞争力,进而促进销售增长。 本文将围绕一个具体的连锁店零售支付结算案例…...

企业消费采购成本和员工体验如何实现“鱼和熊掌“的兼得?
有企业说企业消费采购成本和员工体验的关系好比是“鱼和熊掌”,无法兼得? 要想控制好成本就一定要加强管控,但是加强管控以后,就会很难让员工获得满意的体验度。如果不加以管控,员工自由度增加了,往往就很难…...

发表EI论文相当于SCI几区?
EI(工程索引)本身并不进行分区,它是一个收录工程领域高质量文献的数据库,与SCI(科学引文索引)的分区制度不同。然而,在非正式的学术评价中,有时人们会将EI与SCI的分区进行比较。 虽…...

STFT短时傅里叶变换MTLAB简析
代码: 解释: 如果信号x有Nx个时间样本,短时傅里叶变换的结果矩阵s有k列; k的计算方式如图所示,M是窗函数的长度,L是重叠长度。 此符号是向下取整符号。 短时傅里叶变换的结果矩阵s的行数与参数‘FFTLength’…...

海致科技实施实习生面试
一、面试内容 注:此次是电话面试 1.是XX先生吗 2.你是有考虑转实施的吗? 3.请讲一下你对项目部署实施的理解和掌握 4.用过数据库,会编写SQL语句吗? 5.讲一下SQL的常用关键字 6.了解SQL中的函数吗?谈谈函数 7.多…...

论文阅读之旋转目标检测ARC:《Adaptive Rotated Convolution for Rotated Object Detection》
论文link:link code:code ARC是一个改进的backbone,相比于ResNet,最后的几层有一些改变。 Introduction ARC自适应地旋转以调整每个输入的条件参数,其中旋转角度由路由函数以数据相关的方式预测。此外,还采…...

面向对象(Java)
构造方法只能在对象实例化的时候调用 this可以作为方法参数,表示调用方法的当前对象 this可以作为方法返回值,表示返回当前对象 封装 通过方法访问数据,隐藏类的实现细节 static:类对象共享,类加载时产生,…...

I/O多路复用
参考面试官:简单说一下阻塞IO、非阻塞IO、IO复用的区别 ?_unix环境编程 阻塞io和非阻塞io-CSDN博客 同步阻塞(BIO) BIO 以流的方式处理数据 应用程序发起一个系统调用(recvform),这个时候应用程序会一直阻塞下去&am…...

线性代数基础概念:向量空间
目录 线性代数基础概念:向量空间 1. 向量空间的定义 2. 向量空间的性质 3. 基底和维数 4. 子空间 5. 向量空间的例子 总结 线性代数基础概念:向量空间 向量空间是线性代数中最基本的概念之一,它为我们提供了一个抽象的框架,…...

php 抓取淘宝商品评论数据 json
要抓取淘宝商品评论数据,你可以使用PHP的cURL库来发送HTTP请求并获取JSON格式的数据。 API接入流程:需要开放平台或者是封装接口注册账号,并申请相应的API使用权限,以获取必要的密钥和接口文档。获取接口使用权限:接入…...

Java 7新特性深度解析:提升效率与功能
文章目录 Java 7新特性深度解析:提升效率与功能一、Switch中添加对String类型的支持二、数字字面量的改进三、异常处理(捕获多个异常)四、增强泛型推断五、NIO2.0(AIO)新IO的支持六、SR292与InvokeDynamic七、Path接口…...

RHEL9找不到/var/log/dmesg日志文件问题
问题描述 在Rocky Linux 9 服务器上查看启动日志,发现没有/var/log/dmesg文件。 dmesg是什么? dmesg(diagnostic messages)用于打印kernel ring buffer的所有消息。 kernel会将开机信息存储在ring buffer中,如果开机时来不及查看启动信息&…...

是什么让以太坊从众多公链中脱颖而出
以太坊从众多公链中脱颖而出,成为区块链和加密货币领域的一个重要玩家,主要是由于以下几个关键因素: 智能合约: 以太坊是第一个广泛实施智能合约的区块链平台,智能合约允许在区块链上自动执行合同条款,无需…...

HarmonyOS--路由管理--组件导航 (Navigation)
文档中心 什么是组件导航 (Navigation) ? 1、Navigation是路由容器组件,一般作为首页的根容器,包括单栏(Stack)、分栏(Split)和自适应(Auto)三种显示模式 2、Navigation组件适用于模块内和跨模块的路由切换,一次开发࿰…...

【Linux 命令】文件比较 diff
diff 命令是 Unix 和类 Unix 系统(如 Linux 和 macOS)中用于比较文件内容差异的一个非常有用的命令行工具。它可以逐行比较两个文件的内容,并输出它们之间的差异。这些差异通常以行为单位显示,并且会标记出哪些行是唯一的、添加的…...

猫头虎分享[可灵AI」官方推荐的驯服指南-V1.0
猫头虎分享[可灵AI」官方推荐的驯服指南-V1.0 猫头虎是谁? 大家好,我是 猫头虎,别名猫头虎博主,擅长的技术领域包括云原生、前端、后端、运维和AI。我的博客主要分享技术教程、bug解决思路、开发工具教程、前沿科技资讯、产品评…...

你的硬盘知道的太多:你以为你的秘密真的被删除了吗?
某一天你收到了朋友发给你的一个秘密文件,在看完之后,为了不被别人发现,你决定将文件毁尸灭迹! 你选中文件名称 / 右键 / 删除,好了,文件已经消失了。但你是懂电脑的,知道文件此时还在回收站里面…...

虚拟机的网络配置
📑打牌 : da pai ge的个人主页 🌤️个人专栏 : da pai ge的博客专栏 ☁️ 每一步都向着梦想靠近,坚持就是胜利的序曲 一 …...

ONLYOFFICE8.1版本桌面编辑器简单测评
ONLYOFFICE官网链接:在线PDF查看器和转换器 | ONLYOFFICE ONLYOFFICE介绍:https://www.onlyoffice.com/zh/office-suite.aspx OnlyOffice 是一款免费且开源的 Office 协作办公套件,支持桌面端和移动端等多平台,由一家领先的 IT 公…...

PDF内存如何变小,PDF内存压缩,PDF内存变小怎么调整
在数字化时代,pdf已成为工作、学习和生活中不可或缺的文件格式。它以其跨平台兼容性和安全性受到广大用户的喜爱。然而,随着pdf文件中嵌入的图片、图形和文本内容的增多,文件大小往往会变得相当可观,给文件的传输和存储带来一定的…...

深⼊理解MySQL Innodb存储引擎的缓冲池、事务、索引底层工作原理,掌握 MySQL 主从同步,读写分离技术以及集群的搭建,具备分库分表,SQL调优经验
深入理解MySQL的InnoDB存储引擎是数据库管理员和开发人员的重要技能。以下是对InnoDB存储引擎的缓冲池、事务、索引以及主从同步、读写分离技术和集群搭建的详细原理介绍: ### InnoDB存储引擎 1. **缓冲池(Buffer Pool)**: - 缓冲池是InnoDB存储引擎…...

《HelloGitHub》第 99 期
兴趣是最好的老师,HelloGitHub 让你对编程感兴趣! 简介 HelloGitHub 分享 GitHub 上有趣、入门级的开源项目。 github.com/521xueweihan/HelloGitHub 这里有实战项目、入门教程、黑科技、开源书籍、大厂开源项目等,涵盖多种编程语言 Python、…...