【C++】多态(详解)

news2024/12/23 4:51:42

前言:今天学习的内容可能是近段时间最难的一个部分的内容了,C++的多态,这部分内容博主认为难度比较大,各位一起慢慢啃下来。

💖 博主CSDN主页:卫卫卫的个人主页 💞
👉 专栏分类:高质量C++学习 👈
💯代码仓库:卫卫周大胖的学习日记💫
💪关注博主和博主一起学习!一起努力!
在这里插入图片描述


目录标题

  • 什么是多态
  • 什么是虚函数
  • 多态的定义及实现
    • 1.多态构成的条件
    • 2. 虚函数的重写
    • 3.协变
    • 4.析构函数的重写
    • 5. override 和 final
    • 6. 重载、覆盖(重写)、隐藏(重定义)的对比
  • 抽象类
  • 多态的原理
    • 虚函数表
    • 虚表指针的内容
    • 引用和指针如何实现多态
    • 动态绑定与静态绑定
    • 虚函数表存放位置
  • 单继承和多继承中的虚拟表


什么是多态

多态的概念:多态(polymorphism)是C++中面向对象编程的一个重要概念,它指的是同一种消息(方法调用)在不同的对象上产生不同的行为。这种特性使得程序设计更加灵活,提高了代码的可扩展性和可维护性。(通俗来说,就是多种形态, 具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态)。


什么是虚函数

在C++中,虚函数是一种特殊的成员函数,用于实现多态性。通过将基类的成员函数声明为虚函数,可以在派生类中对该函数进行重写。当通过基类指针或引用调用虚函数时,实际调用的是相应派生类中的函数。

虚函数的声明和定义如下:

class Base//基类
{
public:
    virtual void foo() {
        // 函数实现
    }
};

在基类的函数声明前加上virtual关键字,就将该函数声明为虚函数。派生类可以选择重写基类的虚函数,使用相同的函数签名来定义派生类中的函数:

class Derived : public Base//派生类 
{
public:
    void foo() override {
        // 函数实现
    }
};

注意,在派生类中重写虚函数时,可以使用override关键字显式标注,以增强代码可读性。

使用虚函数时,需要通过基类指针或引用来调用虚函数。根据指针或引用所指向的具体对象类型,调用的将是相应对象的虚函数实现。

例如:

Base* base = new Derived();
base->foo(); // 调用的是Derived的foo()函数

在上述示例中,通过基类指针base指向Derived对象,并调用虚函数foo(),实际调用的是Derived类中的foo()函数。

总结来说,虚函数实现了在基类中声明一个函数,使其可以在派生类中被重写,并能通过基类指针或引用调用派生类对象的对应实现,从而实现多态性。


多态的定义及实现

1.多态构成的条件

C++中的多态性是指在相同的函数签名下,通过基类指针或引用调用不同的派生类对象时,能够实现不同的行为。在C++中,实现多态性需要满足以下三个条件:

  1. 存在继承关系:多态性需要至少有一个基类和一个或多个派生类。
  2. 基类函数为虚函数:基类中的函数必须声明为虚函数,以便在派生类中进行重写。子类父类都有这个虚函数 + 子类的虚函数与父类虚函数的函数名/参数/返回值 都相同 。
  3. 使用基类指针或引用:通过基类指针或引用来调用派生类对象的函数,实现函数的动态绑定。

下面是一个示例代码,演示了多态性的实现:

class Animal //基类
{
public:
    virtual void sound() 
    {
        cout << "Animal is making a sound." << endl;
    }
};

class Cat : public Animal//派生类 
{
public:
    virtual void sound() {
        cout << "Cat is meowing." << endl;
    }
};

class Dog : public Animal//派生类  
{
public:
    virtual void sound() {
        cout << "Dog is barking." << endl;
    }
};

void func(Animal& s)//接受对象为父类的指针或者引用,你传递的是父类就调用父类的函数,传递的是子类就调用子类的函数
{
    s.sound();
}

void test1()
{
    Animal s1;
    Cat s2;
    Dog s3;
    func(s1);
    func(s2);
    func(s3);
}

void test2()
{
    Animal* animal1 = new Animal();//当基类的指针指向派生类的时候,只能操作派生类中从基类中继承过来的数因据和基类自身的数据
    Animal* animal2 = new Cat();
    Animal* animal3 = new Dog();

    animal1->sound(); // Animal is making a sound.
    animal2->sound(); // Cat is meowing.
    animal3->sound(); // Dog is barking.

    delete animal1;
    delete animal2;
    delete animal3;

}
int main() 
{
    test1();
    test2();
    return 0;
}

在上面的代码中,Animal类是基类,Cat和Dog类是派生类。基类Animal中的sound函数被声明为虚函数,而派生类Cat和Dog中重写了该函数。通过使用基类指针animal1、animal2和animal3,分别指向不同的派生类对象,实现了多态性。
注: 在test1中接受对象为父类的指针或者引用,你传递的是父类就调用父类的函数,传递的是子类就调用子类的函数。
在这里插入图片描述


2. 虚函数的重写

在C++中,虚函数的重写是指在派生类中对基类中已有的虚函数进行重新定义。通过重写虚函数,派生类可以改变基类中的函数实现,使其符合派生类的特定需求。

虚函数的重写要求派生类中的函数具有与基类中虚函数完全相同的函数签名(即函数名、参数列表和返回类型都一致)。可以使用override关键字显式标注派生类中的函数,以增强代码的可读性和可靠性。在刚刚的例子中我们通过派生类的Dog和Cat重写了基类中的虚函数sound


3.协变

在C++中,协变(covariant)指的是派生类中重写虚函数的返回类型与基类中的虚函数返回类型具有相关性。换句话说,派生类中重写的虚函数可以返回基类函数返回类型的派生类型。

在早期的C++标准中,派生类中重写虚函数的返回类型必须与基类函数的返回类型完全相同。但是在C++11标准引入了协变的概念,对于返回类型是指针或引用的虚函数,允许派生类中的返回类型是基类返回类型的派生类型。

:协变是子类虚函数与父类虚函数返回值类型不同,但子类和父类的返回值类型也必须是父子关系指针和引用。

以下是一个示例:

class Animal {
public:
    virtual Animal* clone() 
    {
        cout << "Animal clone" << endl;
        return new Animal;
    }
};

class Dog : public Animal 
{
public:
    virtual Dog* clone() override 
    {
        cout << "Dog clone" << endl;
        return new Dog;
    }
};

int main() {
    Dog s1;
    Animal* s2 = s1.clone();//基类接受派生类的虚函数的返回值构造对象
    return 0;
}

在这个例子中,基类Animal有一个虚函数clone,返回一个指向基类的指针。派生类Dog重写了clone函数,并返回一个指向派生类Dog的指针。

当通过基类指针调用clone函数时,根据派生类的类型,会返回相应的指针类型。也就是说,通过协变,Dog*指针会被正确返回。

总结起来,协变允许派生类中的虚函数返回类型与基类中的虚函数返回类型具有相关性,使得处理继承关系时更加灵活和准确。


4.析构函数的重写

在C++中,析构函数(Destructor)也可以被重写。重写析构函数是为了在派生类中定义自己的清理操作。

基类的析构函数通常应该声明为虚函数,以确保正确地调用派生类的析构函数。这是因为当使用基类指针或引用指向派生类对象时,通过基类指针或引用删除对象时,应该调用派生类的析构函数来释放派生类对象的资源。

以下是一个示例:

#include <iostream>
using namespace std;

class Animal {
public:
    virtual ~Animal() {
        cout << "Animal destructor" << endl;
    }
};

class Dog : public Animal {
public:
    virtual ~Dog() override {
        cout << "Dog destructor" << endl;
    }
};

int main() {
    Animal* animal = new Dog();//先调用子类的析构,在调用父类的析构
    delete animal;
    return 0;
}

在这个例子中,基类Animal的析构函数被声明为虚函数。派生类Dog重写了析构函数。

当通过基类指针删除派生类对象时,会正确调用派生类的析构函数。也就是说,首先调用派生类的析构函数,然后调用基类的析构函数。

输出结果为:

Dog destructor
Animal destructor

这表明析构函数按照派生类到基类的顺序被调用。

总结起来,C++中的析构函数可以被重写,在派生类中定义自己的清理操作。为了确保正确地调用派生类的析构函数,基类的析构函数通常应该声明为虚函数。


5. override 和 final

在C++11标准中,overridefinal是两个特殊的关键字,用于修饰成员函数。

  1. override关键字用于表示覆盖基类的虚函数。在C++中,当一个派生类的成员函数与基类的虚函数具有相同的名称和参数列表时,可以使用override关键字显式地告诉编译器这是一个覆盖函数。这样做的好处是可以提醒开发者在派生类中是否正确地覆盖了基类的虚函数。如果函数签名不匹配,编译器会给出错误提示。例如:
class Base {
public:
    virtual void foo() const;
};

class Derived : public Base {
public:
    void foo() const override;  // override关键字表示覆盖基类的虚函数
};
  1. final关键字用于表示禁止派生类进一步覆盖函数。在C++中,可以通过在基类的虚函数后面加上final关键字来禁止派生类进一步覆盖该函数。这样做的好处是可以防止派生类无意中修改这个函数的行为。例如:
class Base {
public:
    virtual void foo() const final;  // final关键字表示禁止派生类进一步覆盖该虚函数
};

class Derived : public Base {
public:
    // 下面的代码会导致编译错误,因为foo()函数被标记为final,无法再被派生类覆盖
    // void foo() const;
};

总之,overridefinal关键字是在C++11中引入的,用于增强对虚函数覆盖的控制。override关键字表示派生类覆盖基类的虚函数,final关键字表示禁止派生类进一步覆盖函数。


6. 重载、覆盖(重写)、隐藏(重定义)的对比

在C++中,有三种不同的函数特性:重载、覆盖(重写)和隐藏(重定义),它们的区别如下:

  1. 重载(Overload):重载是指在同一个作用域内,根据函数的参数类型和/或数量的不同,可以定义多个同名函数。重载函数在调用时根据传入的参数类型和/或数量来决定使用哪个函数。重载函数可以在同一个类中或者不同的类中定义。例如:
void foo(int x);
void foo(float x);
  1. 覆盖(重写,Override):覆盖是指派生类中的函数覆盖了基类中的虚函数,实现了多态性。派生类中的函数必须具有和基类中虚函数相同的名称、参数列表和返回类型,而且在基类中该虚函数必须被声明为virtual。在运行时,根据对象的实际类型来确定使用哪个函数。例如:
class Base {
public:
    virtual void foo();
};

class Derived : public Base {
public:
    void foo() override;
};
  1. 隐藏(重定义,Hide):隐藏是指派生类中的函数屏蔽了基类中的同名函数,不具有多态性。派生类中的函数必须具有和基类中被隐藏的函数相同的名称,但是参数列表和返回类型可以不同。在编译时,根据对象的静态类型来确定使用哪个函数。例如:
class Base {
public:
    void foo(int x);
};

class Derived : public Base {
public:
    void foo(float x);
};

总结:

  • 重载是根据函数的参数类型和/或数量来决定使用哪个函数,主要是静态多态性。
  • 覆盖是指派生类中的函数覆盖了基类中的虚函数,实现了动态多态性。
  • 隐藏是指派生类中的函数屏蔽了基类中的同名函数,不具有多态性。
    在这里插入图片描述

抽象类

在C++中,抽象类是一个不能被直接实例化的类。它只能作为其他类的基类来派生出新的类。抽象类包含至少一个纯虚函数,也可以包含非纯虚函数。

纯虚函数是一个没有实现的虚函数,它通过在函数声明的末尾使用 “= 0” 来指定。纯虚函数的存在使得抽象类无法被实例化,因为任何一个派生类都必须实现所有纯虚函数,才能被实例化。

抽象类主要用于定义公共的接口,而具体的实现则由派生类来完成。它可以作为一种设计工具,用于实现多态性和封装性。在实际应用中,抽象类常常作为基类被其他具体类继承使用。

使用C++中的抽象类需要以下步骤:

  1. 创建一个抽象类:使用class关键字定义一个类,并在需要的成员函数前声明成纯虚函数。至少有一个成员函数是纯虚函数,用于使类成为抽象类。
class AbstractClass {
public:
    virtual void pureVirtualFunction() = 0; // 纯虚函数
    virtual void virtualFunction() { // 非纯虚函数
        // 具体实现
    }
};
  1. 派生一个具体类:从抽象类派生一个具体的子类,该子类必须实现抽象类中的所有纯虚函数。
class ConcreteClass : public AbstractClass {
public:
    void pureVirtualFunction() override {
        // 实现纯虚函数
    }
};
  1. 实例化具体类:通过具体类实例化对象,可以直接调用抽象类中定义的非纯虚函数,或者通过指针或引用调用纯虚函数。
ConcreteClass obj;
obj.virtualFunction();

AbstractClass* ptr = new ConcreteClass();
ptr->pureVirtualFunction();
delete ptr;

通过使用抽象类,可以定义一个通用接口,而具体的实现则由派生类完成。这样可以提高代码的可维护性,支持多态性,并且遵循面向对象的封装性原则。


多态的原理

虚函数表

C++中的虚函数表(Virtual Function Table,简称vtable)是用于实现多态的一种机制。每个含有虚函数的类都会有一个对应的虚函数表,用于存储该类的虚函数的地址。

虚函数表是一个由函数指针组成的数组,每个函数指针指向相应的虚函数的地址。当一个对象被创建后,一个指向该对象对应的虚函数表的指针(通常称为虚表指针,vptr)会被添加到对象的内存布局中。

当通过基类指针或引用调用虚函数时,编译器会将其替换为通过虚函数表来调用相应的虚函数。具体过程如下:

  1. 编译器根据对象的类型找到它的虚函数表。
  2. 根据函数在虚函数表中的位置索引,调用相应的虚函数。

通过虚函数表,C++实现了运行时多态性,允许在运行时根据对象的实际类型来调用相应的虚函数,而不是根据指针或引用的静态类型来调用相应的函数。这为面向对象的程序设计提供了灵活性和可扩展性。

下面我看看一个面试题:
在这里插入图片描述
这里我们直接说结果:在这里插入图片描述

为什么是8呢?因为我们刚刚提到的虚函指针在这里出现了,我们去监视窗口看看,是不是真的有这个虚函数指针的存在。
在这里插入图片描述

结合刚刚的例子,这里就充分解释了,为什么这对象b中会是8个字节了,当一个对象被创建后,一个指向该对象对应的虚函数表的指针(通常称为虚表指针,vptr)会被添加到对象的内存布局中。


虚表指针的内容

话不多说先看一个例子:
在下图中我们会发现,你创建的对象中,第一个存的就是虚表指针的地址,且发现我们查看虚表指针的地址会发现,虚函数的地址依次存储在该虚表中。

在这里插入图片描述


既然这样,我们接着去派生类中查看他的虚表指针有什么不同,如下图所示:

  1. 基类Animal对象s1和派生类Cat对象s2虚表是不一样的,这里我们发现sound完成了重写,所以s2的虚表中存的是重写的Animal::sound,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法
  2. 另外fn继承下来后是虚函数,所以放进了虚表,f1也继承下来了,但是不是虚函数,所以不会放进虚表。
  3. 虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr。
  4. 总结一下派生类的虚表生成:a.先将基类中的虚表内容拷贝一份到派生类虚表中 b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数 c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。
  5. 这里还有一个童鞋们很容易混淆的问题:虚函数存在哪的?虚表存在哪的? 答:虚函数存在虚表,虚表存在对象中。注意上面的回答的错的。但是很多童鞋都是这样深以为然的。注意虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针。
    在这里插入图片描述

引用和指针如何实现多态

我们之前提到过多态,可以通过传过来的基类的对象或者派生类的对象,来调用其对应的虚函数,但是他是如何识别的呢?
在这里插入图片描述
:这里我们在强调一下,你无论对基类取地址,还是父类取地址,如果有虚函数,那么这个地址都是他虚函数表指针的地址。

这里我们就可以分析,当我们传递基类的对象地址时,就会去基类的虚表指针中去调用对应的虚函数。当我们传递派生类对象的地址时,会将派生类的内容切割掉(切片),然后去调用其虚表指针的地址,然后再去调用对应的虚函数。


动态绑定与静态绑定

在C++中,动态绑定(dynamic binding)和静态绑定(static binding)是两种不同的绑定方式,它们是实现多态性的关键。

静态绑定是在编译时确定函数调用的地址。当使用对象的指针或引用调用函数时,编译器会根据指针或引用的类型来确定调用的函数。这种绑定方式是静态的,因为调用的函数在编译时就已经确定了。静态绑定通常用于非虚函数。

动态绑定是在运行时确定函数调用的地址。当使用对象的指针或引用调用虚函数时,编译器会在运行时根据对象的实际类型来确定调用的函数。这种绑定方式是动态的,因为调用的函数直到运行时才能确定。动态绑定实现了多态性,因为可以通过基类的指针或引用调用派生类的成员函数。动态绑定通常用于虚函数。

动态绑定通过虚函数表(virtual function table)来实现。每个带有虚函数的对象都有一个虚函数表,其中存储了虚函数的地址。当调用虚函数时,通过对象的指针或引用访问虚函数表,并根据对象的实际类型调用正确的函数。

总结起来,静态绑定在编译时确定函数调用的地址,而动态绑定在运行时根据对象的实际类型确定函数调用的地址,实现了多态性。在使用函数时,如果希望实现多态性,需要使用虚函数和动态绑定。


虚函数表存放位置

在 C++ 中,虚函数表的存放位置通常是在 可执行目标文件 的 只读数据段 ( .rodata )1。具体来说,虚函数表指针( vptr )存储在对象实例的内存中,而虚函数表本身则存储在可执行文件的只读数据段中。这意味着直到程序启动并加载可执行文件时,虚函数表的地址才会确定。

举个例子验证一下:

class Base {
public:
	virtual void func1() { cout << "Base::func1" << endl; }
	virtual void func2() { cout << "Base::func2" << endl; }
private:
	int a;
};
 
void func()
{}
 
int main()
{
	Base b1;
	Base b2;
	static int a = 0;
	int b = 0;
	int* p1 = new int;
	const char* p2 = "hello world";
	printf("静态区:%p\n", &a);
	printf("栈:%p\n", &b);
	printf("堆:%p\n", p1);
	printf("代码段:%p\n", p2);
	printf("虚表:%p\n", *((int*)&b1));//虚表的地址是存放在类对象的头4个字节上因此我们对其进行强转,取地址就会得到虚表的位置了,
	printf("虚函数地址:%p\n", & Base::func1);
	printf("普通函数:%p\n", func);
}

如下图所示,我们会发现,虚表的地址和代码段的地址十分相近,因此我们可以得出虚表存放在代码段这个位置,和我们前面的结论相似。
在这里插入图片描述


单继承和多继承中的虚拟表

在C++中,每个类都有一个虚函数表(virtual function table),用于存储该类的虚函数的地址。当一个类中定义了虚函数时,编译器会为该类创建一个虚函数表,并将虚函数表的地址存储在对象的内存布局中。当通过指针或引用访问对象的虚函数时,编译器会根据对象的内存布局中存储的虚函数表的地址,找到对应的虚函数并调用。

对于单继承关系,每个类只有一个虚函数表。当子类继承父类时,子类会继承父类的虚函数表,并在自己的虚函数表中添加自己的虚函数。当通过子类的指针或引用访问虚函数时,编译器会根据子类对象的内存布局中存储的虚函数表的地址,找到对应的虚函数并调用。

对于多继承关系,每个类都有自己的虚函数表。当一个类通过多个父类进行多继承时,每个父类会有自己的虚函数表,并在子类的内存布局中存储这些虚函数表的地址。在访问虚函数时,编译器会根据对象的内存布局中存储的虚函数表的地址,找到对应的虚函数并调用。

需要注意的是,多继承中可能会出现虚函数表的冲突或者大小不一致的问题,编译器会根据不同的实现采取不同的解决方案来处理这些问题。

:每一个虚函数都会放到虚表里面,但是有的编译器并不会显示一些虚函数!!!


好啦,今天的内容就到这里啦,下期内容预告搜索树的学习与模拟实现.


结语:进阶的内容有点繁杂,大家一起加油呐!。


🌏🗺️ 这里祝各位接下来的每一天好运连连 💞💞

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1890221.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

绝地求生PUBG服务器延迟太高 购买领取响应时间长怎么解决

绝地求生PUBG是一款特别热门的射击类吃鸡游戏&#xff0c;游戏还有多张地图可供玩家选择&#xff0c;玩家们需要乘坐飞机空投跳伞至不同的各个角落&#xff0c;赤手空拳寻找武器&#xff0c;车辆以及物资&#xff0c;并在多种多样的地形中展开战斗。想要取得胜利&#xff0c;我…

微信小程序转发朋友圈详细教程

微信小程序转发朋友圈功能&#xff0c;官方说的很官方&#xff0c;容易踩坑 官方链接戳这里 想分享朋友圈必须要分享好友 onShareTimeline() { } 想要生效必须先定义 onShareAppMessage() { } /*** 用户点击右上角分享*/onShareAppMessage() { },onShareTimeline() { } 简单…

应对SQL注入攻击:保障网站安全的策略

在互联网的广阔天地中&#xff0c;网站安全始终是站长用户和企业开发者不可忽视的重要议题。其中&#xff0c;SQL注入攻击作为一种常见的网络攻击手段&#xff0c;严重威胁着网站的数据安全和业务稳定。什么是SQL注入攻击&#xff0c;我们该如何应对这种攻击呢&#xff1f;今天…

广州外贸建站模板

Yamal外贸独立站wordpress主题 绿色的亚马尔Yamal外贸独立站wordpress模板&#xff0c;适用于外贸公司建独立站的wordpress主题。 https://www.jianzhanpress.com/?p7066 赛斯科Sesko-W外贸建站WP主题 适合机械设备生产厂家出海做外贸官网的wordpress主题&#xff0c;红橙色…

互联网应用主流框架整合之SpringCloud微服务治理

微服务架构理念 关于微服务的概念、理念及设计相关内容,并没有特别严格的边界和定义,某种意义上说,适合的就是最好的,在之前的文章中有过详细的阐述,微服务[v1.0.0][Spring生态概述]、微服务[设计与运行]、微服务[v1.0.0][服务调用]、微服务[开发生命周期]、微服务[面临的…

Unity之创建与导出PDF

内容将会持续更新&#xff0c;有错误的地方欢迎指正&#xff0c;谢谢! Unity之创建与导出PDF TechX 坚持将创新的科技带给世界&#xff01; 拥有更好的学习体验 —— 不断努力&#xff0c;不断进步&#xff0c;不断探索 TechX —— 心探索、心进取&#xff01; 助力快速…

最靓丽的C++开源通知弹框SnoreToasts自动监听软件及网页通知

SnoreToasts&#xff0c;作为一款轻量级的C开源项目&#xff0c;为开发者提供了一个便捷的方式来在Windows操作系统上展示通知弹框&#xff08;Toast Notifications&#xff09;。 特点与优势 轻量级&#xff1a;SnoreToasts采用了简洁的代码设计&#xff0c;避免了不必要的依…

SQLServer:从数据类型 varchar 转换为 numeric 时出错。

1.工作要求 计算某两个经纬度距离 2.遇到问题 从数据类型 varchar 转换为 numeric 时出错。 3.解决问题 项目版本较老&#xff0c;使用SQLServer 2012 计算距离需执行视图&#xff0c;如下&#xff1a; SET QUOTED_IDENTIFIER ON SET ANSI_NULLS ON GO ALTER view vi_ord…

「前端」快速排序算法演示

快速排序算法演示。 布局描述 一个简单的HTML页面,用户可以在其中输入一系列用逗号分隔的数字。 一个CSS样式表,提供了一个美观大方的布局和样式。 一个JavaScript脚本,实现了快速排序算法,并在用户点击按钮时对输入的数字进行排序,并显示结果。 效果演示 核心代码 <…

PyPDF2拆分PDF文件的高级应用:指定拆分方式

本文目录 前言一、拆分方式选择1、代码讲解2、实现效果图3、完整代码前言 前两篇文章,分别讲解了将使用PyPDF2将PDF文档分割成为单个页面、在分割PDF文档时指定只分割出指定页面,如果你还没有看过,然后有需要的话,可以去看一下,我把文章链接贴到这里: PyPDF2拆分PDF文件…

苹果可能与谷歌大模型合作,马斯克xAI下个月推出Grok-2,比尔·盖茨:Scaling Law快要走到尽头

ChatGPT狂飙160天&#xff0c;世界已经不是之前的样子。 更多资源欢迎关注 1、苹果被曝 Gemini 模型今秋或融入苹果智能生态系统 苹果知名爆料人马克古尔曼&#xff08;Mark Gurman&#xff09;最新透露&#xff0c;苹果公司将于今年秋季宣布与Alphabet旗下的谷歌的大模型Gem…

计算机网络网络层复习题2

一. 单选题&#xff08;共22题&#xff0c;100分&#xff09; 1. (单选题)如果 IPv4 数据报太大&#xff0c;会在传输中被分片&#xff0c;对分片后的数据报进行重组的是&#xff08; &#xff09;。 A. 中间路由器B. 核心路由器C. 下一跳路由器D. 目的主机 我的答案: D:目的…

如何学好自动化测试

1. 什么是自动化测试 自动化测试是使用脚本和工具来执行测试任务&#xff0c;以替代手工测试过程。它可以提高效率、减少人工错误&#xff0c;并增加测试覆盖率。在软件开发过程中&#xff0c;自动化测试已经成为了不可或缺的一部分。 自动化测试主要有以下好处&#xff1a; …

《人人都是产品经理》:大产品

《人人都是产品经理》&#xff1a;大产品 产品之大时间之大空间之大&#xff1a;商业、产品、技术设计之大以写书为例 团队之大 回答一个问题 产品经理应该是管理者嘛&#xff1f;优点在于&#xff1a;缺点在于&#xff1a;总结&#xff1a; 如何让团队更加开心总结 产品之大 …

Android线性布局的概念与属性

线性布局(LinearLayout)是Android中最简单的布局方式&#xff0c;线性布局方式会使得所有在其内部的控件或子布局按一条水平或垂直的线排列。如图所示&#xff0c;图a是纵向线性布局示意图&#xff0c;图b是横向线性布局示意图。 a&#xff09;纵向线性布局示意图 …

JAVA每日作业day7.1-7.3小总结

ok了家人们前几天学了一些知识&#xff0c;接下来一起看看吧 一.API Java 的 API &#xff08; API: Application( 应用 ) Programming( 程序 ) Interface(接口 ) &#xff09; Java API 就是 JDK 中提供给我们使用的类&#xff0c;这些类将底层 的代码实现封装了起来&#x…

仓库货物管理系统

摘 要 随着信息技术的迅猛发展&#xff0c;大数据已经成为推动各行各业变革的重要力量。特别是在物流仓储领域&#xff0c;大数据技术的应用不仅能够显著提升仓库货物管理的效率&#xff0c;还能够优化库存管理、减少成本、提高客户满意度。因此&#xff0c;基于大数据的仓库货…

第二十一章 网络编程

​ 一、网络的相关概念 1. 网络通信 &#xff08;1&#xff09;网络通信&#xff1a;将 数据 通过网络从一台设备传输到另一台设备 &#xff08;2&#xff09;java.net 包下提供了一系列的类或接口&#xff0c;完成网络通信 2. 网络 概念&#xff1a;两台或多台设备通过一定…

git配置ssh-keygen -t rsa -c“xxxx@xxxx.com.cn出现Too many arguments.解决办法

git配置ssh-keygen -t rsa -c"xxxxxxxx.com.cn出现Too many arguments.解决办法 问题描述 配置Git公钥私钥时候输入命令ssh-keygen -t rsa -c"xxxxxxxx.com.cn出现Too many arguments. 解决办法&#xff1a; 提示输入的参数格式不正确&#xff0c;需要注意这几个地…

鸿蒙开发设备管理:【@ohos.settings (设置数据项名称)】

设置数据项名称 说明&#xff1a; 本模块首批接口从API version 8开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 本模块提供设置数据项的访问功能相关接口的说明及示例。 导入模块 import settings from ohos.settings;settings.getUri…