突破编程_C++_基础教程(继承与多态)

1 继承

继承是面向对象编程的一个基本概念,它允许一个类(派生类、子类)继承另一个类(基类、父类)的属性和方法。继承可以减少代码冗余,提高代码重用性,并且有助于创建更复杂的类结构。

1.1 继承的基本用法

要在派生类中继承基类,只需在派生类定义的时候列出基类的名称,并指定继承方式(公有、保护或私有):

class BaseClass 
{  // 基类的成员  
};  class DerivedClass : public BaseClass 
{  // 派生类的成员  
};

在上面代码中,Derived 类公有地继承了 Base 类。这意味着 Base 类中的公有成员和保护成员在 Derived 类中保持原有的访问权限(公有和保护),而 Base 类中的私有成员在 Derived 类中是不可访问的。
派生类是基类的一个特殊化版本。这意味着派生类拥有基类所有的属性和方法,同时还可以定义自己的新属性和方法。这种关系通常被称为 “IS-A” 关系,即派生类 “是一种” 基类。如下为样例代码:

class BaseClass
{
public:int getVal(){return m_val;}protected:int m_val = 0;
};class DerivedClass : public BaseClass
{
public:void derivedFunc(){m_val = 2;					//OK:拥有基类的属性m_derivedVal = getVal();	//OK:拥有基类的方法}private:int m_derivedVal = 1;
};

在上面代码中,继承类 DerivedClass 的成员函数 derivedFunc() 可以使用基类的属性和方法。

1.2 继承的类型

C++支持三种继承方式:
公有继承(Public Inheritance)
当使用公有继承时,基类的公有成员和保护成员在派生类中保持原有的访问权限,而基类的私有成员在派生类中是不可访问的。这是最常见的继承方式。如下为样例代码:

class BaseClass
{
public:int getVal1(){return m_val1;}protected:int m_val1 = 0;private:int m_val2 = 0;
};class DerivedClass : public BaseClass		//公有继承
{
public:DerivedClass(){m_val2 = 1;		//错误:基类的私有成员在派生类中是不可访问的}
};DerivedClass derivedObj;
int val1 =  derivedObj.getVal1();		//OK:公有继承下,基类的公有成员在派生类中是公有的,外部可以访问
derivedObj.m_val1 = 1;					//错误:公有继承下,基类的保护成员在派生类中是保护的,外部不可以访问	

保护继承(Protected Inheritance)
在保护继承中,基类的公有成员和保护成员在派生类中变为保护成员,而基类的私有成员在派生类中是不可访问的。如下为样例代码:

class BaseClass
{
public:int getVal1(){return m_val1;}protected:int m_val1 = 0;
};class DerivedClass : protected BaseClass		//保护继承
{
};DerivedClass derivedObj;
int val1 =  derivedObj.getVal1();		//错误:保护继承下,基类的公有成员在派生类中是保护的,外部不可以访问

私有继承(Private Inheritance)
在私有继承中,基类的公有成员和保护成员在派生类中变为私有成员,而基类的私有成员在派生类中是不可访问的。如下为样例代码:

class BaseClass
{
public:int getVal1(){return m_val1;}protected:int m_val1 = 0;
};class DerivedClass : private BaseClass		//私有继承
{
};DerivedClass derivedObj;
int val1 =  derivedObj.getVal1();		//错误:私有继承下,基类的公有成员在派生类中是私有的,外部不可以访问

继承方式比较

继承方式基类公有成员基类保护成员基类私有成员
公有继承继承为公有成员继承为保护成员不继承
保护继承继承为保护成员继承为保护成员不继承
私有继承继承为私有成员继承为私有成员不继承

1.3 派生类的构造函数和析构函数

在派生类对象初始化时,派生类的构造函数需要调用基类的构造函数来初始化基类部分。这可以通过成员初始化列表实现。在销毁派生类对象时,派生类的析构函数会在派生类部分析构之后自动调用基类的析构函数。
注意:创建对象时,先调用基类的构造函数,再调用派生类的构造函数;销毁对象时,先调用派生类的析构函数,再调用基类的析构函数。

1.3.1 派生类的构造函数

派生类的构造函数负责初始化从基类继承的成员和派生类自己新增的成员。在没有显式的指定基类构造函数的情况下,派生类的构造函数会默认调用基类的无参构造函数来初始化基类部分。如下为样例代码:

#include <iostream>  class BaseClass
{
public:BaseClass(){printf("call BaseClass()\n");}BaseClass(int val){printf("call BaseClass(int val)\n");}
};class DerivedClass : public BaseClass
{
public:DerivedClass(){printf("call DerivedClass()\n");}DerivedClass(int val){printf("call DerivedClass(int val)\n");}
};int main() {DerivedClass derivedObj(2);return 0;
}

上面代码的输出为:

call BaseClass()
call DerivedClass(int val)

在这个例子中,DerivedClass 类继承自 BaseClass 类。DerivedClass 类与 BaseClass 类包含两个构造函数:无参构造函数以及有参数构造函数。在 main 函数中,创建 DerivedClass 类的对象时,由于传入了参数 2 ,所以调用的是其有参数构造函数,但是因为没有显式的指定基类构造函数,所以会默认的调用基类的无参构造函数(即使基类也有一个相似的有参数构造函数)。
如果需要调用基类的有参数构造函数,则需要做如下修改(这也是常用的调用方式):

#include <iostream>  class BaseClass
{
public:BaseClass(){printf("call BaseClass()\n");}BaseClass(int val){printf("call BaseClass(int val)\n");}
};class DerivedClass : public BaseClass
{
public:DerivedClass(){printf("call DerivedClass()\n");}DerivedClass(int val) : BaseClass(val)		//这里显式的调用了基类的有参数构造函数{printf("call DerivedClass(int val)\n");}
};int main() {DerivedClass derivedObj(2);return 0;
}

上面代码的输出为:

call BaseClass(int val)
call DerivedClass(int val)

1.3.2 派生类的析构函数

在C++中,派生类的析构函数用于执行派生类对象在销毁时所需的清理工作。当派生类对象被销毁时,它的析构函数首先执行,然后是它的基类的析构函数。这是为了确保派生类中的成员在基类之前被正确释放。
如果没有在派生类中定义析构函数,编译器会自动生成一个默认的析构函数。这个默认的析构函数会调用基类的析构函数(如果基类有定义的话)。如下为样例代码:

#include <iostream>  class BaseClass
{
public:BaseClass() {}~BaseClass(){printf("call ~BaseClass()\n");}
};class DerivedClass : public BaseClass
{
public:DerivedClass() {}~DerivedClass(){printf("call ~DerivedClass()\n");}
};int main() 
{	{DerivedClass derivedObj;}return 0;
}

上面代码的输出为:

call ~DerivedClass()
call ~BaseClass()

在上面代码中,当 DerivedClass 类的对象被销毁时(离开作用域后被销毁),首先会调用 DerivedClass 类的析构函数,然后是 BaseClass 类的析构函数。这样确保了派生类中的资源在基类之前被正确释放。
通常,如果在派生类中分配了动态内存(例如使用 new),则应该在派生类的析构函数中释放这些内存。如果基类也分配了动态内存,那么基类的析构函数应该负责释放这些内存。这样,当派生类对象被销毁时,所有的资源都会被正确清理。

1.4 多重继承

多重继承是指一个类可以继承自多个基类。这意味着一个类可以获取多个基类的属性和方法。然而,多重继承也带来了一些复杂性和潜在的问题,如菱形继承、二义性调用和初始化顺序等。
多重继承的简单使用如下:

class BaseClass1
{
protected:int m_baseClass1Val;
};class BaseClass2
{
protected:int m_baseClass2Val;
};class DerivedClass : public BaseClass1, public BaseClass2
{
public:void derivedFunc()	{m_baseClass1Val = 1;	// 拥有 BaseClass1 基类的成员变量m_baseClass2Val = 2;	// 拥有 BaseClass2 基类的成员变量}
};

注意这里的 public、 protected 或 private 关键字决定了基类成员在派生类中的访问权限。

1.4.1 菱形继承

菱形继承( Diamond Inheritance )是C++多重继承中的一个特定情况,它指的是当一个类从两个或更多的类继承,而这些类又有一个共同的基类时,形成的继承结构图看起来像一个菱形。这种情况下,基类在继承层次中被共享,可能会导致一些问题,特别是当基类包含数据成员时。
在C++中,菱形继承的主要问题在于基类数据成员的多份拷贝。如下为样例代码:

class BaseClass
{
protected:int m_baseVal;
};class BaseLeftClass : public BaseClass
{
};class BaseRightClass : public BaseClass
{
};class DerivedClass : public BaseLeftClass, public BaseRightClass
{
};

在这个例子中,DerivedClass 类通过 BaseLeftClass 和 BaseRightClass 间接地继承了两次 BaseClass 类。如果没有特殊处理,BaseClass 类中的成员变量 m_baseVal 将在 DerivedClass 类中存在两份拷贝:一份来自 BaseLeftClass,一份来自 BaseRightClass。这不仅浪费空间,而且可能导致逻辑错误,因为通过 BaseRightClass 和 BaseRightClass 访问到的成员变量 m_baseVal 可能是不同的。
为了解决这个问题,C++ 提供了虚继承(Virtual Inheritance)。虚继承确保在继承层次中,无论基类出现多少次,都只会存在一份基类子对象的拷贝。使用虚继承,上面的例子可以改写为:

class BaseClass
{
protected:int m_baseVal;
};class BaseLeftClass : virtual public BaseClass
{
};class BaseRightClass : virtual public BaseClass
{
};class DerivedClass : public BaseLeftClass, public BaseRightClass
{
};

通过使用 virtual public 的虚继承方式,保证了在 DerivedClass 类中只有一个 BaseClass 子对象的拷贝。这消除了数据冗余,并确保了通过 BaseLeftClass 和 BaseRightClass 访问到的 value 是同一个。

1.4.2 二义性调用

二义性调用发生在多重继承的情况下,当派生类试图调用从多个基类继承而来的同名成员(如方法或属性)时,编译器无法确定应该使用哪个基类的成员。
这种情况通常发生在以下场景中:
(1)当派生类继承自两个基类,并且这两个基类都有一个同名的成员函数。
(3)当派生类继承自一个基类,并且这个基类继承自另外两个也拥有同名成员函数的基类。
如果派生类试图调用这个同名的成员函数,编译器将无法确定应该使用哪个基类的版本,因此会报错。如下为样例代码:

#include <iostream>  class BaseClass1
{
public:void func() {};
};class BaseClass2
{
public:void func() {};
};class DerivedClass : public BaseClass1, public BaseClass2
{
};int main() 
{	DerivedClass* obj = new DerivedClass;obj->func();					// 错误:二义性调用  delete obj;obj = nullptr;return 0;
}

在上面代码中,DerivedClass 类从 BaseClass1 和 BaseClass2 继承,而 BaseClass1 和 BaseClass2 都有一个名为 func 的成员函数。因此,当试图在 DerivedClass 类的对象上调用 func 时,编译器不知道应该调用 BaseClass1::func 还是 BaseClass2::func,从而产生了二义性。使用作用域解析运算符 (::)可以解决上面的二义性调用的问题:

obj->BaseClass1::func();

2 多态

多态( Polymorphism )是面向对象编程的三大基本特性之一,它允许我们使用相同的接口来表示不同类型的对象。在 C++ 中,多态通常通过虚函数( virtual functions )和指针或引用来实现。

2.1 虚函数

虚函数是 C++ 中实现多态的关键机制。通过在基类的成员函数前加上 virtual 关键字,可以将其声明为虚函数。当派生类重写( override )这个虚函数时,就可以通过基类指针或引用来调用派生类的实现,这就是所谓的动态绑定或运行时多态。与之相对应的是静态绑定,即在使用父类指针或引用调用子类对象的成员函数时,如果没有使用虚函数,则会进行静态绑定,从而只能调用父类的成员函数,无法调用子类特有的成员函数。
虚函数的原理主要涉及虚函数表和虚函数指针:
(1)虚函数表
当一个类含有至少一个虚函数时,编译器会为这个类创建一个虚函数表( vtable ),并在每个该类的对象中嵌入一个指向这个虚函数表的指针(通常被称为 vptr )。
虚函数表是一个函数指针数组,其中每个元素都是指向类中定义的虚函数的指针。每个类,包括它的所有派生类,都会有自己的虚函数表。当派生类重写基类的虚函数时,派生类的虚函数表会包含指向这些重写函数的指针。
(2)虚函数指针
虚函数指针( virtual function pointer ,简称为 vptr )是一个隐藏的成员变量,它存在于包含至少一个虚函数的类的对象中。 vptr 指向一个虚函数表( vtable ),该表包含了类中所有虚函数的地址。虚函数表是一个函数指针数组,每个元素都指向一个虚函数的实现。
vptr 的存在是实现多态性的关键,它允许程序在运行时动态地确定应该调用哪个类的虚函数实现。当通过基类指针或引用调用一个虚函数时,实际调用的是 vptr 所指向的虚函数表中的函数。
虚函数的具体工作原理如下:
(1)定义虚函数:在基类中声明一个或多个虚函数。
(2)创建虚函数表:编译器为包含虚函数的类创建一个虚函数表。这个表是一个函数指针数组,每个元素指向类中的一个虚函数。
(3)初始化虚函数指针:当创建类的对象时,编译器会为该对象的虚函数指针成员变量分配内存,并初始化虚函数指针以指向该类的虚函数表。
(4)动态绑定:当通过基类指针或引用调用虚函数时,程序会查找虚函数指针所指向的虚函数表,并调用表中对应的函数。这个查找过程是在运行时进行的,因此被称为动态绑定。
注意点:
虚函数表与虚函数指针都是由是编译器实现,对程序员是透明的。
虚函数表与虚函数指针的存在对内存与性能有一定影响,因为当创建类的对象时,编译器会为该对象的虚函数指针成员变量分配内存,并且每次调用虚函数都需要查找虚函数表。然而,这种性能开销通常是可以接受的,因为它提供了强大的多态性支持。

2.2 多态的使用

C++中的多态主要有两种类型:静态多态和动态多态。
静态多态
也称为编译时多态或早绑定( Early Binding )。这主要通过函数重载( Function Overloading )和模板( Templates )来实现。在编译时,编译器就能确定应该调用哪个函数。
动态多态
也称为运行时多态或晚绑定( Late Binding )。这是通过虚函数( Virtual Functions )和继承来实现的。动态多态也被称为函数重写( Function Overriding )。在运行时,程序根据对象的实际类型来确定应该调用哪个函数。
如下为样例代码:

#include <iostream>  class BaseClass
{
public:void overloadFunc(){printf("BaseClass overloadFunc()\n");}virtual void overrideFunc(){printf("BaseClass overrideFunc()\n");}
};class DerivedClass : public BaseClass
{
public:void overloadFunc()			// 函数重载{printf("DerivedClass overloadFunc()\n");}void overrideFunc()			// 函数重写{printf("DerivedClass overrideFunc()\n");}
};int main() 
{	BaseClass* obj = new DerivedClass;obj->overloadFunc();					// 调用基类的函数((DerivedClass*)obj)->overloadFunc();	// 强制类型转换后调用继承类的重载函数obj->overrideFunc();					// 调用继承类的重写函数delete obj;obj = nullptr;return 0;
}

上面代码的输出为:

BaseClass overloadFunc()
DerivedClass overloadFunc()
DerivedClass overrideFunc()

在上面代码中, BaseClass 是一个基类,它有一个成员函数 overloadFunc() 以及一个虚函数 overrideFunc()。 DerivedClass 是 BaseClass 的派生类,它重载了 overloadFunc() 函数,并且重写了 overrideFunc() 函数。在 main 函数中,使用 BaseClass 基类的指针变量创建了一个 DerivedClass 派生类的对象,并调用其 overloadFunc() 以及 overrideFunc(),注意使用强制类型转换后才能真正调用到继承类的重载函数,而重写函数则可以直接调用。

2.3 多态与构造函数、析构函数

构造函数不能被定义为虚函数:
原因主要有以下几点:
(1)执行时机:构造函数在对象创建时立即执行,它是对象初始化的一部分。而虚函数是在运行时通过动态绑定来确定的,这需要对象的虚函数表已经初始化。在构造函数执行时,虚函数表尚未建立,因此无法执行虚函数。
(2)对象完整性:在构造函数执行期间,对象还没有完全构造完成。如果构造函数是虚函数,那么当派生类的构造函数被调用时,它的基类部分可能还没有完全初始化。这会导致对象的状态不一致,进而引发错误。
(3)初始化顺序:在继承层次中,派生类构造函数在调用自身之前会首先调用基类的构造函数。如果基类构造函数是虚函数,则会去查找派生类的重写虚函数,从而导致初始化顺序的混乱。
建议将基类的析构函数定义为虚函数:
将析构函数定义为虚函数是一个重要的代码构建技术点,特别是在设计基类时。这是因为在多态性的场景中,当使用基类指针或引用指向派生类对象时,如果没有虚析构函数,可能会导致派生类对象的析构过程不完整,从而引发资源泄漏和其他问题。
当基类的析构函数不是虚函数时,如果通过基类指针删除派生类对象,只会调用基类的析构函数,而不会调用派生类的析构函数。这会导致派生类对象中的资源(如动态分配的内存)没有被正确释放,从而产生资源泄漏。
通过将基类的析构函数声明为虚函数,可以确保当使用基类指针或引用删除派生类对象时,派生类的析构函数也会被调用。这是多态性的一个关键方面,它允许在运行时确定应该调用哪个类的析构函数。
如下为样例代码:

#include <iostream>  class BaseClass
{
public:// 将析构函数声明为虚函数  virtual ~BaseClass(){printf("virtual ~BaseClass() \n");}
};class DerivedClass : public BaseClass
{
public:~DerivedClass(){printf("virtual ~DerivedClass() \n");}
};int main() 
{	BaseClass* obj = new DerivedClass;delete obj;obj = nullptr;// 如果 BaseClass 的析构函数不是虚函数,这里只会调用 BaseClass 的析构函数,  // 而不会调用 DerivedClass 的析构函数,导致资源泄漏。  // 如果 BaseClass 的析构函数是虚函数,则会先调用 DerivedClass 的析构函数,  // 然后调用 BaseClass 的析构函数,确保资源被正确释放。 return 0;
}

上面代码的输出为:

virtual ~DerivedClass()
virtual ~BaseClass()

在上面代码中,BaseClass 类的析构函数被声明为虚函数。当通过基类指针 obj 删除派生类 DerivedClass 的对象时,由于析构函数是虚函数,所以会先调用 DerivedClass 的析构函数,然后再调用 BaseClass 的析构函数。这样,派生类中的资源能够被正确释放,避免了资源泄漏。
因此,通常建议在设计基类时将析构函数定义为虚函数,以确保在删除派生类对象时能够正确地调用析构函数链。

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

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

相关文章

自动驾驶轨迹规划之kinodynamic planning

欢迎大家关注我的B站&#xff1a; 偷吃薯片的Zheng同学的个人空间-偷吃薯片的Zheng同学个人主页-哔哩哔哩视频 (bilibili.com) 本文PPT来自深蓝学院《移动机器人的运动规划》 目录 1.kinodynamic的背景 2. old-school pipline 3.example 1.kinodynamic的背景 kinodynami…

java之jvm详解

JVM内存结构 程序计数器 Program Counter Register程序计数器(寄存器) 程序计数器在物理层上是通过寄存器实现的 作用&#xff1a;记住下一条jvm指令的执行地址特点 是线程私有的(每个线程都有属于自己的程序计数器)不会存在内存溢出 虚拟机栈(默认大小为1024kb) 每个线…

LeetCode、739. 每日温度【中等,单调栈】

文章目录 前言LeetCode、739. 每日温度【中等&#xff0c;单调栈】题目链接及分类思路单调栈 资料获取 前言 博主介绍&#xff1a;✌目前全网粉丝2W&#xff0c;csdn博客专家、Java领域优质创作者&#xff0c;博客之星、阿里云平台优质作者、专注于Java后端技术领域。 涵盖技…

(delphi11最新学习资料) Object Pascal 学习笔记---第5章第1节(动态数组)

5.1.4 动态数组 ​ 在传统的Pascal中&#xff0c;数组的大小是固定的&#xff0c;并且在声明数据类型时限制了元素的数量。然而&#xff0c;Object Pascal支持动态数组的直接和本地实现。 注解&#xff1a;“直接实现动态数组” 与使用指针和动态内存分配来获得类似效果的方法…

ROS笔记三:话题

目录 简要介绍 ROS话题通信机制的一些核心概念和流程&#xff1a; 话题通信的流程如下 ROS常见的topic命令行指令 发布话题 1.创建ROS节点并初始化 2.创建话题发布者 3.创建消息实例并设置内容 4.将消息发布出去 5.保持节点运行 订阅话题 初始化ROS节点和创建NodeHan…

PMP考试之20240214

1、你同时管理着公司的六个项目。两个项目属于类似类型&#xff0c;而其他四个项目则完全不同。你的职位是&#xff1f; A.项目组合经理 B.项目集经理 C.项目经理 D.项目协调员 答案&#xff1a;A 解析&#xff1a;在项目组合管理中&#xff0c;一组相关或非相关的计划和…

二次元自适应动态引导页

源码介绍 二次元自适应动态引导页&#xff0c;HTMLJSCSS&#xff0c;记事本修改&#xff0c;上传到服务器即可&#xff0c;也可以本地双击index.html查看效果 下载地址 https://wfr.lanzout.com/isRem1o7bfcb

MockServer 服务框架设计

大部分现有的 mock 工具只能满足 HTTP 协议下简单业务场景的使用。但是面对一些复杂的业务场景就显得捉襟见肘&#xff0c;比如对 socket 协议的应用进行 mock&#xff0c;或者对于支付接口的失败重试的定制化 mock 场景。 为解决上述问题&#xff0c;霍格沃兹测试学院设计并研…

零基础学编程怎么入手,中文编程工具构件箱之多页面板构件用法教程,系统化的编程视频教程上线

零基础学编程怎么入手&#xff0c;中文编程工具构件箱之多页面板构件用法教程&#xff0c;系统化的编程视频教程上线 一、前言 今天给大家分享的中文编程开发语言工具资料如下&#xff1a; 编程入门视频教程链接 http://​ https://edu.csdn.net/course/detail/39036 ​ …

回乡后发现大家的消费水平都在升级

今天大年初四&#xff0c;来湖南第7天。 大年初四走亲戚&#xff0c;到城里姨妈家做客&#xff0c;我惊讶地发现&#xff0c;这里的消费风向正在发生一场悄然的变革。 以往&#xff0c;我认为大城市的消费水平代表着潮流和品质&#xff0c;然而这次我却发现小城市的消费观念正在…

re:从0开始的CSS之旅 14. 显示模式的切换

1. 两个属性 display 属性可以用于转换元素的显示模式 可选值&#xff1a; block 转换为块元素 inline 转换为行内元素 inline-block 转换为行内块元素 none 不显示元素&#xff0c;并且不占用元素的位置 visibility 属性用于设置元素是否显示 可选值&#xff1a; visible 显示…

文档类图像的智能识别,文档分类自定义分类器

文档类图像的智能识别是利用人工智能技术对文档图像进行自动识别和信息提取的过程。在实际应用中&#xff0c;文档分类是文档类图像识别的一个重要环节&#xff0c;而自定义分类器则可以提高文档分类的准确性和适应性。本文将介绍文档分类自定义分类器的相关概念和方法。 …

springboot/ssm知名作家信息管理系统Java文学作品展示管理系统

springboot/ssm知名作家信息管理系统Java文学作品展示管理系统 开发语言&#xff1a;Java 框架&#xff1a;springboot&#xff08;可改ssm&#xff09; vue JDK版本&#xff1a;JDK1.8&#xff08;或11&#xff09; 服务器&#xff1a;tomcat 数据库&#xff1a;mysql 5.…

下一代块存储重新定义任务关键型存储架构

HPE 宣布全面推出基于 HPE Alletra Storage MP 构建的 HPE GreenLake for Block Storage 第 3 版&#xff0c;提供业界首款分解式横向扩展块存储&#xff0c;并提供 100% 数据可用性保证。这种独特的块存储产品由共享一切存储架构提供支持&#xff0c;并通过 HPE GreenLake 云平…

智能门锁代码实现之连接硬件的步骤

准备硬件和开发环境&#xff1a;确保你拥有所需的硬件&#xff08;如微控制器、门锁控制电路、通信接口等&#xff09;&#xff0c;并设置好 C 语言的开发环境。对于 Arduino 等开源硬件平台&#xff0c;你可能需要安装特定的 IDE&#xff08;集成开发环境&#xff09;和驱动程…

(算法3)二分查找

朴素二分查找 最直接的二分查找&#xff0c;有序&#xff0c;查找数组中的某个元素 这种方法是有局限性的&#xff1a;只可以查找升序的数组&#xff0c;且要查找的元素是一个 注意&#xff1a;mid(中点&#xff09;的计算应该是&#xff1a;left(right-left)/2 (个数是偶数时…

接口测试06 -- pytest接口自动化封装Loggin实战

1. 接口关键字封装 1.1 基本概念 接口关键字封装是指:将接口测试过程中常用的操作、验证封装成可复用的关键字(或称为函数、方法),以提高测试代码的可维护性和可复用性。 1.2 常见的接口关键字封装方式 1. 发送请求:封装一个函数,接受参数如请求方法、URL、请求头、请求…

Sketch 99.1 for macOS

Sketch 99.1 for macOS 概述 这个程序是对矢量绘图的创新性和焕然一新的看法。它特意采用了极简主义的设计&#xff0c;基于一个大小无限、图层自由的绘图空间&#xff0c;没有调色板、面板、菜单、窗口和控件。 此外&#xff0c;它提供了强大的矢量绘图和文本工具&#xff0c;…

django通过指定用户手机号查询外键所关联的数据,倒序查询

django通过指定用户手机号查询外键所关联的数据 在Django中&#xff0c;可以通过使用filter方法和双下划线语法来查询外键所关联的数据。以下是一种常见的方法&#xff1a; from your_app.models import User, ForeignKeyModel# 假设User模型有一个名为phone的字段&#xff…

基于Spring Boot的美容院管理系统设计与实现,计算机毕业设计(带源码+论文)

源码获取地址&#xff1a; 码呢-一个专注于技术分享的博客平台一个专注于技术分享的博客平台,大家以共同学习,乐于分享,拥抱开源的价值观进行学习交流http://www.xmbiao.cn/resource-details/1757434902285987841