【C++进阶篇】像传承家族宝藏一样理解C++继承

文章目录

须知

💬 欢迎讨论:如果你在学习过程中有任何问题或想法,欢迎在评论区留言,我们一起交流学习。你的支持是我继续创作的动力!

👍 点赞、收藏与分享:觉得这篇文章对你有帮助吗?别忘了点赞、收藏并分享给更多的小伙伴哦!你们的支持是我不断进步的动力!
🚀 分享给更多人:如果你觉得这篇文章对你有帮助,欢迎分享给更多对C++感兴趣的朋友,让我们一起进步! 

《深入剖析C++继承:从基础到进阶的完整指南》

 1. C++继承前言与背景

1.1 前言

C++是一个功能强大的面向对象编程语言,它不仅支持过程式编程,还在此基础上提供了许多用于构建复杂软件系统的面向对象特性。继承是C++中最为核心的概念之一,它允许我们通过现有的类(基类)创建新的类(派生类),从而实现代码的重用和扩展。继承是面向对象编程的三个基本特性之一(另外两个是封装和多态),在设计模式、软件架构和大型系统开发中起着至关重要的作用。

理解和应用C++继承的概念对于编写高效、可维护和可扩展的代码至关重要。C++的继承不仅仅是一个简单的“类与类之间的关系”,它涉及到如何组织和管理对象之间的共享数据、方法以及如何利用多态实现代码的灵活性。因此,C++继承的深入理解对程序员来说是必须的,它能够帮助开发者更好地设计类的层次结构,提升软件系统的复用性和扩展性。

1.2 背景

继承源于面向对象编程的基本思想,即通过创建类的层次结构,模拟现实世界中的事物关系。在现实世界中,物体之间往往存在父子关系、包含关系、继承关系等。C++的继承机制正是通过类与类之间的继承关系来模拟这些现实中的关系。继承使得开发者能够从一个基类派生出多个派生类,从而共享基类的行为,并在需要时对其进行扩展或修改。

在C++中,继承是通过class关键字和访问修饰符(如publicprotectedprivate)来实现的,基类(父类)提供了一些公有和保护成员,派生类(子类)可以继承这些成员。继承还允许派生类重写基类的方法(方法重写),并能够通过虚函数实现运行时多态性,这是C++继承特性的重要组成部分。

C++继承的强大之处在于它不仅仅支持单一继承,还支持多继承,这使得它可以适应更复杂的类关系。通过使用虚拟继承,C++避免了传统多继承中可能出现的“钻石继承”问题。此外,C++继承支持访问控制(如publicprotectedprivate继承),从而为开发者提供了灵活的类设计和组织结构的能力。

然而,C++继承的设计和使用也存在一些挑战,特别是在多继承和虚继承的场景下。理解如何合理使用继承关系,避免继承层次过深,避免继承滥用,是程序员需要掌握的关键技能。

C++继承的关键要点:

  1. 代码重用:继承使得子类能够复用父类的属性和方法,减少重复代码。
  2. 扩展性:通过继承,子类可以扩展或修改父类的行为,从而实现系统的扩展。
  3. 多态性:继承和虚函数的结合使得C++能够实现运行时多态,从而使代码更加灵活和动态。
  4. 多继承与虚继承:C++支持多继承和虚继承,这为开发者提供了强大的功能,但也增加了代码设计的复杂度。
  5. 访问控制:C++提供了不同的继承访问权限(publicprotectedprivate),允许开发者控制基类成员的访问权限,确保程序设计的封装性和安全性。

 2.引言:C++继承的核心意义

继承是面向对象编程的一个关键特性,它能够使得代码更加简洁、可扩展和易维护。在C++中,继承不仅仅是类之间的关系,更是构建复杂系统的基石。通过继承,我们可以在一个类中共享另一个类的功能,而不需要重复编写相同的代码。

在这篇博客中,我们将深入探讨C++中的继承,包括其基础概念、应用场景、常见问题以及一些进阶技巧。通过示例和图示,您将能够更好地理解继承的各个方面,并能够在项目中有效运用。 

3.继承基本概念与定义

3.1 什么是C++继承——从基本概念开始

继承 (inheritance) 机制是面向对象程序设计 使代码可以复用 的最重要的手段,它允许程序员在
持原有类特性的基础上进行扩展 ,增加功能,这样产生新的类,称派生类。继承 呈现了面向对象
程序设计的层次结构 ,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,
承是类设计层次的复用。
3.1.1 示例代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;
class Person
{
public:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "peter"; // 姓名int _age = 18;//年龄
};
class Student : public Person
{
protected:int _stuid; // 学号
};
class Teacher : public Person
{
protected:int _jobid; // 工号
};
int main()
{Student s;Teacher t;s.Print();t.Print();return 0;
}

输出:

name:peter
age:18
name:peter
age:18

在上面的代码中,Student类继承了Person类,因此可以访问Person类中的Print()方法。 

 3.2 继承的定义

继承在 C++ 中的定义主要通过以下格式实现:

class 子类名 : 继承方式 基类名 {
    // 子类的成员
}; 

继承语法:

  • 使用publicprivateprotected访问权限来决定继承的可访问性。
    • public继承:基类的公有成员在派生类中仍然是公有的。
    • protected继承:基类的公有成员在派生类中变为受保护的。
    • private继承:基类的公有成员在派生类中变为私有的。

 

示例代码:

class Teacher : public Person {
protected:int _jobid;  // 工号
};int main() {Student s;Teacher t;s.Print();t.Print();return 0;
}

派生类StudentTeacher都继承基类(父类)Person类的成员方法函数Print(),通过s.Print()t.Print()输出 Student 和 Teacher 对象的姓名和年龄。

4. 继承中的访问权限

4.1 基类成员在派生类中的访问权限

基类的 publicprotected 和 private 成员在派生类中的访问权限取决于继承方式。下面是不同继承方式下的访问权限表:

 从表中可以看出基类的private成员在派生类(子类)始终不可见,而基类的public成员和protected成员的是否能被访问取决于本身成员的访问权限与继承方式,两个取继承方式最坎坷的一个。

注意:1-> 如果需要基类的某个成员在派生类中可访问但不希望类外部访问,则可以将其设置为 protected,这样可以更好地控制访问权限。

2-> 使用关键字 class 时默认的继承方式是 private ,使用 struct 时默认的继承方式是 public 不过
最好显示的写出继承方式

 

4.2  基类和派生类对象赋值转换

在C++中,基类和派生类对象的赋值转换是一个比较常见的操作场景。通常情况下,派生类对象可以赋值给基类对象,或者通过基类的指针或引用来操作派生类对象。这种转换机制使得C++在继承结构中实现了多态和代码复用。但需要注意的是,基类对象不能直接赋值给派生类对象。 

4.2.1 派生类对象赋值给基类对象 

 派生类对象包含了基类的成员,因此派生类对象赋值给基类对象时将属于基类对象的那一部分赋值给基类对象。这里有个形象的说法叫切片(切割)。寓意把派生类中父类那部分切来赋值过去。

示例代码:

#include<iostream>
#include<string>
using namespace std;class Person
{
protected:string _name; // 姓名string _sex;  // 性别int _age; // 年龄
};
class Student : public Person
{
public:int _No; // 学号
};
void Test()
{Student sobj;// 1.子类对象可以赋值给父类对象/指针/引用Person pobj = sobj;Person* pp = &sobj;Person& rp = sobj;//2.基类对象不能赋值给派生类对象sobj = pobj;//error// 3.基类的指针可以通过强制类型转换赋值给派生类的指针pp = &sobj;Student * ps1 = (Student*)pp; // 这种情况转换时可以的。ps1->_No = 10;pp = &pobj;Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问题ps2->_No = 10;
}
 4.2.2 基类指针与引用转换

派生类对象可以赋值给基类的指针或引用,这是实现多态的重要前提条件。通过基类指针或引用,程序可以在运行时动态绑定到派生类的成员函数。这种方式允许我们在不需要修改代码的情况下扩展程序的功能。

 示例代码:
 

#include<iostream>
#include<string>
using namespace std;class Person
{
public:virtual void Print()//具有二义性,所以添加了Virtual{cout << "Person: " << _name << endl;}
protected:string _name="Jack";
};class Student:public Person
{
public:void Print()override{cout << "Student: " << _age <<" name:"<<_name << endl;}
private:int _age=20;
};void PrintPersonInfo(Person& p) {p.Print();  // 基类引用调用虚函数,实现多态
}int main() {Student s;PrintPersonInfo(s);  // 输出 "Student: 20, name: Jack"return 0;
}

在这个例子中,我们通过基类 Person 的引用调用 Student 类中的 Print() 函数,实现了运行时多态。派生类对象 s 被传递给基类引用 p,并正确调用了 Student 类的重写函数 Print()

 4.2.3 强制类型转换

在某些特殊情况下,基类指针或引用可能需要转换为派生类的指针或引用。C++ 提供了 dynamic_caststatic_cast 等多种类型转换方式。在继承关系中,使用 dynamic_cast 进行安全的类型转换尤为重要,特别是在处理多态时。

Person* pp = new Student();  // 基类指针指向派生类对象
Student* dc = dynamic_cast<Student*>(pp);  // 安全的向下转换
if (dc) 
{
    dc->Print();
}
else {
    cout << "Type conversion failed!" << endl;

dynamic_cast 在运行时进行类型检查,确保转换是安全的。如果转换失败,将返回 nullptr,从而避免越界访问的风险。 

 5. 继承中的作用域与成员访问

5.1 作用域的独立性与同名成员的隐藏

在继承关系中,基类与派生类各自拥有独立的作用域。如果派生类中定义了与基类成员同名的变量或函数,基类的同名成员将被隐藏,这种现象称为隐藏(Hiding)也叫重定义同名成员在派生类中会覆盖基类中的成员,导致基类成员无法被直接访问。

示例代码:

#include<iostream>
#include<string>
using namespace std;// Student的_num和Person的_num构成隐藏关系,可以看出这样代码虽然能跑,但是非常容易混淆class Person{protected:string _name = "小李子"; // 姓名int _num = 111;//  身份证号};class Student : public Person{public:void Print(){cout << " 姓名:" << _name << endl;cout << " 身份证号:" << Person::_num << endl;cout << " 学号:" << _num << endl;}protected:int _num = 999; // 学号};int main(){Student s;s.Print();return 0;}

 输出:

 姓名:小李子
 身份证号:111
 学号:999

 从输出结果可以看出,Student 类中定义了一个 _num 变量,它隐藏了基类 Person 中的同名变量。为了访问基类的 _num,我们使用了 Person::_num 来显式地指定访问基类中的成员。这样可以避免由于成员同名而导致的混淆。

实际开发中不建议写同名的变量名或函数名。

 5.1.1 函数的隐藏

在C++中,函数隐藏指的是子类中定义的一个与父类中已有的成员函数具有相同名称和参数列表的函数,导致父类的函数在子类中被“隐藏”或“遮蔽”的现象。这种情况通常发生在子类中定义了一个与父类中同名的函数时,父类的函数就不再可见或无法被直接调用,除非通过特定方式(如使用作用域解析符::)显式访问。 

示例代码:

class Teacher
{
public:void Print()const{cout << "Teacher:" << _tel << endl;}
private:string _tel="123456678";
};class Student:public Teacher
{
public:void Print()const{cout << "Student:" << _tel<<" age:"<<age<<" _name:"<<_name << endl;}
private:string _tel = "0123456789";int age = 18;string _name = "Mark";
};int main()
{Student s;s.Print();return 0;
}

 输出:

Student:0123456789 age:18 _name:Mark

从结果可以看出: 派生类Student中的_tel="012356789"隐藏父类Teacher中的_tel="123456678",若大家强制想访问父类Teacher,可以使用Teacher::_tel。

与函数重载区别:

重载作用于同一个作用域,而隐藏作用于不同的作用域,因此隐藏不构成重载(Overloading

 构成函数隐藏的条件不是很苛刻,只需要函数名或变量名相同即可。

5.2 派生类的默认成员函数

在 C++ 中,当我们不显式定义类的构造函数、拷贝构造函数、赋值运算符和析构函数时,编译器会自动为我们生成这些函数。这些自动生成的函数在派生类中也会涉及到对基类成员的操作,因此在继承体系中了解这些默认成员函数的调用规则非常重要。

5.2.1 构造函数的调用顺序

 派生类对象构造过程中,基类的对象会首先调用构造函数进行初始化,其次派生类再调用构造函数进行初始化。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。

 示例代码:

class Teacher
{
public:Teacher(const string& name):_name(name){cout << "Teacher constructor called!" << endl;}
private:string _name;
};class Student :public Teacher
{
public:Student(const string& name, int id):Teacher(name)//使用匿名构造函数完成初始化,_id(id){cout << "Student constructor called!" << endl;}
private:int _id;
};int main()
{Student s("Bob", 20241128);return 0;
}

输出:

Teacher constructor called!
Student constructor called!

从结果可以看出,先调用父类的构造,然后再调用派生类的构造函数。这种调用顺序确保基类的成员在派生类构造之前就已经被正确初始化。

 5.2.2  拷贝构造函数与赋值运算符的调用

当派生类对象被拷贝时,基类的拷贝构造函数会先被调用,然后才是派生类的拷贝构造函数。同样,赋值运算符的调用顺序也遵循这一规则:基类的赋值运算符会先于派生类的赋值运算符被调用。 

 示例代码:

class Teacher
{
public:Teacher(const string& name):_name(name){}//拷贝构造函数Teacher(const Teacher& t){_name = t._name;cout << "Teacher copy constructor called!" << endl;}//赋值运算符重载Teacher& operator=(const Teacher& t){_name = t._name;cout << "Teacher assignment operator called!" << endl;return *this;}
protected:string _name;
};class Student :public Teacher
{
public:Student(const string& name, int id):Teacher(name)//使用匿名构造函数完成初始化, _id(id){}//拷贝构造函数Student(const Student& s):Teacher(s)//基类没有默认构造函数,则派生类的构造函数必须在初始化列表中显式调用基类的构造函数。{_id = s._id;cout << "Student copy constructor called!" << endl;}//赋值运算符重载Student& operator=(const Student& s){Teacher::operator=(s);//先调用基类的赋值运算符重载_id = s._id;cout << "Student assignment operator called!" << endl;return *this;}protected:int _id;
};int main()
{Student s1("Bob", 20241128);Student s2=s1;//拷贝构造Student s3("Jack", 2345);s1 = s3;//赋值运算符重载return 0;
}

输出:

Teacher copy constructor called!
Student copy constructor called!
Teacher assignment operator called!
Student assignment operator called!

从结果可以看出基类的拷贝构造和赋值运算符重载优先级优于派生类。为了保证派生类对象的完整性,派生类的拷贝构造函数和赋值运算符必须调用基类的相应函数,确保基类成员正确处理。

5.2.3 析构函数调用顺序

 与构造函数的调用顺序相反,析构函数的调用顺序是先调用派生类的析构函数,然后再调用基类的析构函数。这确保了派生类的资源先被释放,然后基类的资源才能安全地释放。

示例代码:

class Person
{
public:Person(const int& age) : _age(age) {}~Person(){cout << "Person destructor called!" << endl;}
protected:int _age;
};class Student:public Person
{
public:Student(const int& age, const string& name):Person(age), _name(name){}~Student(){cout << "Student destructor called!" << endl;}
protected:string _name;
};int main()
{Student s(20, "Mark");return 0;
}

输出:

Student destructor called!
Person destructor called!

从结果可以看出,派生类Student先调用析构函数,进行类对象清理资源,然后再是基类Person调用析构函数,完成类对象资源清理,从而确保所有派生类的资源被正确释放。

5.2.4 虚析构函数

在继承体系中,若希望基类指针指向派生类对象,并通过该指针安全地释放对象,基类的析构函数应当定义为虚函数。否则,仅会调用基类的析构函数,导致派生类资源没有正确释放,从而引发内存泄漏。 

示例代码:

考虑以下示例,展示了没有虚析构函数时会导致资源未释放的情况: 

#include <iostream>
using namespace std;class Base {
public:Base() { cout << "Base Constructor" << endl; }~Base() { cout << "Base Destructor" << endl; }  // 非虚析构函数
};class Derived : public Base {
public:Derived() { cout << "Derived Constructor" << endl; }~Derived() { cout << "Derived Destructor" << endl; }  // 派生类析构函数
};int main() {Base* basePtr = new Derived();delete basePtr;  // 只调用Base的析构函数,没有调用Derived的析构函数return 0;
}

输出:

Base Constructor
Derived Constructor
Base Destructor 

解释:

 如上所示,当delete basePtr被调用时,基类的析构函数Base::~Base()被调用,但派生类的析构函数Derived::~Derived()没有被调用。这样,Derived类中的资源(例如动态分配的内存、文件句柄等)就没有被正确释放,导致内存泄漏。

正确的做法是将基类的析构函数声明为虚函数: 

#include <iostream>
using namespace std;class Base {
public:Base() { cout << "Base Constructor" << endl; }virtual ~Base() { cout << "Base Destructor" << endl; }  // 虚析构函数
};class Derived : public Base {
public:Derived() { cout << "Derived Constructor" << endl; }~Derived() { cout << "Derived Destructor" << endl; }  // 派生类析构函数
};int main() {Base* basePtr = new Derived();delete basePtr;  // 会先调用Derived的析构函数,再调用Base的析构函数return 0;
}

 输出:

Base Constructor
Derived Constructor
Derived Destructor
Base Destructor

解释:

 在这个例子中,基类的析构函数被声明为虚函数,因此当delete basePtr被调用时,程序会首先调用派生类的析构函数Derived::~Derived(),然后再调用基类的析构函数Base::~Base(),从而确保派生类资源得到正确释放。

总结:

  • 虚析构函数:在继承体系中,基类的析构函数应当声明为虚函数,以确保派生类的析构函数能够在删除基类指针时被正确调用。
  • 内存泄漏:如果基类的析构函数不是虚函数,那么派生类的析构函数不会被调用,可能会导致资源没有得到正确释放,从而引发内存泄漏。 

最后 

  1. 继承是面向对象编程的基础:继承允许通过基类创建派生类,实现代码重用、扩展和模块化设计,是面向对象编程的核心特性之一。

  2. 增强代码复用与扩展性:通过继承,派生类可以复用基类的代码,同时根据需要扩展或修改功能,提高代码的复用性和系统的灵活性。

  3. 虚继承解决多继承问题:C++支持多继承,但为了避免多继承中出现的“钻石继承”问题,虚继承机制确保基类的成员只会被继承一次,避免二义性和资源冲突。

  4. 虚函数和多态性实现动态行为:通过虚函数和多态性,C++使得基类指针或引用可以动态地调用派生类的实现,提高了代码的灵活性和可扩展性。

  5. 方法重写与函数隐藏的区别:方法重写是通过虚函数实现的多态性,而函数隐藏则是子类中定义的同名函数覆盖了父类中的函数,但不支持多态性。

  6. 析构函数必须为虚函数:当基类指针指向派生类对象时,析构函数必须声明为虚函数,以确保派生类的资源能够被正确释放,避免内存泄漏。

  7. 继承设计的最佳实践:继承应遵循简洁、清晰的设计原则,避免过深的继承层次和滥用多继承,确保类之间的关系符合“里氏替换原则”,从而提高代码的可维护性和可扩展性。

总结而言,C++继承是实现高效、灵活、可扩展的软件系统的核心工具,但继承的设计与使用应遵循一定的原则,避免复杂性和误用,从而提升代码质量和系统的可维护性。

 路虽远,行则将至;事虽难,做则必成

 亲爱的读者们,下一篇文章再会!!!

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

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

相关文章

Swin-T图像论文复现

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

扫雷-完整源码(C语言实现)

云边有个稻草人-CSDN博客 在学完C语言函数之后&#xff0c;我们就有能力去实现简易版扫雷游戏了&#xff08;成就感满满&#xff09;&#xff0c;下面是扫雷游戏的源码&#xff0c;快试一试效果如何吧&#xff01; 在test.c里面进行扫雷游戏的测试&#xff0c;game.h和game.c…

Spring Web MVC(详解中)

文章目录 Spring MVC&#xff08;中&#xff09;RESTFul风格设计RESTFul风格概述RESTFul风格特点RESTFul风格设计规范RESTFul风格好处RESTFul风格实战需求分析RESTFul风格接口设计后台接口实现 基于RESTFul风格练习&#xff08;前后端分离模式&#xff09;案例功能和接口分析功…

输入json 达到预览效果

下载 npm i vue-json-pretty2.4.0 <template><div class"newBranchesDialog"><t-base-dialogv-if"addDialogShow"title"Json数据配置"closeDialog"closeDialog":dialogVisible"addDialogShow":center"…

STL算法之基本算法<stl_algobase.h>

STL标准规格中没哟区分基本算法或复杂算法&#xff0c;然后SGI却把常用的一些算法定义于<stl_algobase.h>之中&#xff0c;其他算法定义于<stl_algo.h>之中。以下一一列举这些基本算法。 目录 运用实例 equal,fill,fill_n,iter_swap, lexicographical_compare,m…

dns 服务器简单介绍

dns 服务器分类&#xff1a; 根域名服务器顶级域名服务器权威域名服务器本地域名服务器 dns 的查询过程 国内优秀公共域名 腾讯&#xff1a;DNSPod-免费智能DNS解析服务商-电信_网通_教育网,智能DNS-烟台帝思普网络科技有限公司 119.29.29.29 和 182.254.118.118 阿里&#xf…

AI智算-正式上架GPU资源监控概览 Grafana Dashboard

下载链接 https://grafana.com/grafana/dashboards/22424-ai-gpu-20241127/

CAN详解

CAN简介 • CAN 总线&#xff08; Controller Area Network Bus &#xff09;控制器局域网总线 • CAN 总线是由 BOSCH 公司开发的一种简洁易用、传输速度快、易扩展、可靠性高的串行通信总线&#xff0c;广泛应用于汽车、嵌入式、工业控制等领域 • CAN 总线特征&#xff1a; …

透视投影(Perspective projection)与等距圆柱投影(Equirectangular projection)

一、透视投影 1.方法概述 Perspective projection&#xff08;透视投影&#xff09;是一种模拟人眼观察三维空间物体时的视觉效果的投影方法。它通过模拟观察者从一个特定视点观察三维场景的方式来创建二维图像。在透视投影中&#xff0c;远处的物体看起来比近处的物体小&…

(四)Spring Boot学习——整合修改使用druid连接池

我的是使用springboot3的&#xff0c;对应的有整合的druid-spring-boot-3-starter的jar实现对springboot3的兼容。 <!--******************数据库相关配置************************--> <!-- 1.配置数据库相关的jar包,连接池使用druids上&#xff0c;并引入整合spring…

think php处理 异步 url 请求 记录

1、需求 某网站 需要 AI生成音乐&#xff0c;生成mp3文件的时候需要等待&#xff0c;需要程序中实时监听mp3文件是否生成 2、用的开发框架 为php 3、文件结构 配置路由设置 Route::group(/music, function () {Route::post(/musicLyrics, AiMusic/musicLyrics);//Ai生成歌词流式…

Linux八股积累与笔记

1、iptables 是一个用于配置Linux内核防火墙规则的工具。四表五链&#xff1a;在iptables中&#xff0c;有四个表&#xff08;tables&#xff09;和五个链&#xff08;chains&#xff09;&#xff0c;用于管理不同类型的数据包过滤规则。如下&#xff1a; 表&#xff08;Tabl…

乐鑫发布 esp-iot-solution v2.0 版本

今天&#xff0c;乐鑫很高兴地宣布&#xff0c;esp-iot-solution v2.0 版本已经发布&#xff0c;release/v2.0 分支下的正式版本组件将为用户提供为期两年的 Bugfix 维护&#xff08;直到 2027.01.25 ESP-IDF v5.3 EOL&#xff09;。该版本将物联网开发中常用的功能进行了分类整…

【爬虫框架:feapder,管理系统 feaplat】

github&#xff1a;https://github.com/Boris-code/feapder 爬虫管理系统 feaplat&#xff1a;http://feapder.com/#/feapder_platform/feaplat 爬虫在线工具库 &#xff1a;http://www.spidertools.cn &#xff1a;https://www.kgtools.cn/1、feapder 简介 对于学习 Python…

uni-app 蓝牙开发

一. 前言 Uni-App 是一个使用 Vue.js 开发&#xff08;所有&#xff09;前端应用的框架&#xff0c;能够编译到 iOS、Android、快应用以及各种小程序等多个平台。因此&#xff0c;如果你需要快速开发一款跨平台的应用&#xff0c;比如在 H5、小程序、iOS、Android 等多个平台上…

C语言——海龟作图(对之前所有内容复习)

一.问题描述 海龟作图 设想有一只机械海龟&#xff0c;他在C程序控制下在屋里四处爬行。海龟拿了一只笔&#xff0c;这支笔或者朝上&#xff0c;或者朝下。当笔朝下时&#xff0c;海龟用笔画下自己的移动轨迹&#xff1b;当笔朝上时&#xff0c;海龟在移动过程中什么也不画。 …

【Maven】继承和聚合

5. Maven的继承和聚合 5.1 什么是继承 Maven 的依赖传递机制可以一定程度上简化 POM 的配置&#xff0c;但这仅限于存在依赖关系的项目或模块中。当一个项目的多个模块都依赖于相同 jar 包的相同版本&#xff0c;且这些模块之间不存在依赖关系&#xff0c;这就导致同一个依赖…

Android 性能优化:内存优化(理论篇)

内存作为App程序运行最重要的资源之一&#xff0c;需要运行过程中做到合理的资源分配与回收&#xff0c;不合理的内存占用轻则使得用户应用程序运行卡顿、ANR、黑屏&#xff0c;重则导致用户应用程序发生 OOM&#xff08;out of memory&#xff09;崩溃。喜马直播随着近些年的业…

技能之发布自己的依赖到npm上

目录 开始 解决 步骤一&#xff1a; 步骤二&#xff1a; 步骤三&#xff1a; 运用 一直以为自己的项目在github上有了&#xff08;之传了github&#xff09;就可以进行npm install下载&#xff0c;有没有和我一样萌萌的同学。没事&#xff0c;萌萌乎乎的不犯罪。 偶然的机…

【选择排序和交换排序】直接选择排序、堆排序、冒泡排序、快速排序

【选择排序和交换排序】直接选择排序、堆排序、冒泡排序、快速排序 1. 选择排序1.1 直接选择排序1.1.1详细过程1.1.2 代码实现1.1.3 复杂度和稳定性 1.2 堆排序 2. 交换排序2.1 冒泡排序2.1.1 代码实现2.1.2 复杂度和稳定性 2.2 快速排序——挖坑法2.2.1详细过程2.2.2 代码实现…