【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.mzph.cn/diannao/62270.shtml

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

相关文章

DAMODEL丹摩|部署FLUX.1+ComfyUI实战教程

本文仅做测评体验&#xff0c;非广告。 文章目录 1. FLUX.1简介2. 实战2. 1 创建资源2. 1 ComfyUI的部署操作2. 3 部署FLUX.1 3. 测试5. 释放资源4. 结语 1. FLUX.1简介 FLUX.1是由黑森林实验室&#xff08;Black Forest Labs&#xff09;开发的开源AI图像生成模型。它拥有12…

具体的技术和工具在县级融媒体建设3.0中有哪些应用?

以下是结合数据来看县级融媒体建设3.0的一些情况&#xff1a; 技术应用方面 大数据&#xff1a;人民网舆情数据中心执行主任董盟君提到&#xff0c;通过大数据分析可让融媒体单位快速关注聚焦点&#xff0c;实现智能策划、智能推送、智能传播&#xff0c;推动媒体传播影响力提…

中兴机顶盒B860AV1.1刷机固件升级和教程「适用4/8G版」

准备工作&#xff1a; TTL 线&#xff08;CH340G 按系统版本找到要对应驱动&#xff09;下载 putty 软件拆开电视盒接好 TTL 线&#xff08;2、5、6 针脚对应GND、RX、TX&#xff09;在资源管理器的端口选项下找到 CH340G&#xff0c;记住端口号&#xff08;如 COM4&#xff0…

SeggisV1.0 遥感影像分割软件【源代码】讲解

在此基础上进行二次开发&#xff0c;开发自己的软件&#xff0c;例如&#xff1a;【1】无人机及个人私有影像识别【2】离线使用【3】变化监测模型集成【4】个人私有分割模型集成等等&#xff0c;不管是您用来个人学习 还是公司研发需求&#xff0c;都相当合适&#xff0c;包您满…

QINQ技术

定义 QINQ即802.1q in 802.1q&#xff0c;因为IEEE802.1Q中定义的Vlan Tag域只有12个比特&#xff0c;仅能表示4096个Vlan&#xff0c;随网络发展被用尽&#xff0c;于是在原有带vlan的数据上再携带一层vlan标签用于扩展vlan数目。一般来说外层vlan是公网&#xff0c;内层是私…

linux基础2

声明&#xff01; 学习视频来自B站up主 泷羽sec 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&#…

鸿蒙千帆启新程,共绘数字生态蓝图

华为的鸿蒙千帆起计划&#xff1a;共筑数字未来&#xff0c;学习华为创新之路 在当今全球科技竞争日益激烈的背景下&#xff0c;华为作为中国科技企业的代表&#xff0c;正通过其自主创新的鸿蒙系统&#xff0c;引领一场移动应用生态的变革。鸿蒙千帆起计划&#xff0c;作为华…

Qt-系统相关(2)多线程网络

Qt多线程 在 Qt 中&#xff0c;多线程的处理⼀般是通过 QThread类 来实现。 QThread 代表⼀个在应⽤程序中可以独⽴控制的线程&#xff0c;也可以和进程中的其他线程共享数据。 QThread 对象管理程序中的⼀个控制线程。 QThread 常⽤ API&#xff1a; 使用线程 关于创建线程…

永久免费的PDF万能水印删除工具

永久免费的PDF万能水印删除工具 1.简介 PDF万能水印删除工具&#xff0c;可以去除99.9%的PDF水印。例如&#xff1a;XObject水印&#xff08;含图片水印&#xff09;、文本水印、绘图水印/曲线水印、注释水印、工件水印、剪切路径水印等等。本软件是永久免费&#xff0c;无有…

华三(HCL)和华为(eNSP)模拟器共存安装手册

接上章叙述&#xff0c;解决同一台PC上同时部署华三(HCL)和华为(eNSP&#xff09;模拟器。原因就是华三HCL 的老版本如v2及以下使用VirtualBox v5版本&#xff0c;可以直接和eNSP兼容Oracle VirtualBox&#xff0c;而其他版本均使用Oracle VirtualBox v6以上的版本&#xff0c;…

深度理解进程的概念(Linux)

目录 一、冯诺依曼体系 二、操作系统(OS) 设计操作系统的目的 核心功能 系统调用 三、进程的概念与基本操作 简介 查看进程 通过系统调用获取进程标识符 通过系统调用创建进程——fork() 四、进程的状态 操作系统中的运行、阻塞和挂起 理解linux内核链表 Linux的进…

SQLite 管理工具 SQLiteStudio 3.4.5 发布

SQLiteStudio 3.4.5 版本现已发布&#xff0c;它带来了大量的 bug 修复&#xff0c;并增加了一些小功能。SQLiteStudio 是一个跨平台的 SQLite 数据库的管理工具。 具体更新内容包括&#xff1a; 现在可以使用 Collations Editor 窗口在数据库中注册 Extension-based collatio…

非常简单实用的前后端分离项目-仓库管理系统(Springboot+Vue)part 2

七、创建前端项目 你下载了nodejs吗&#xff1f;从cn官网下载&#xff1a;http://nodejs.cn/download/&#xff0c;或者从一个国外org网站下载&#xff0c;选择自己想要的版本https://nodejs.org/download/release/&#xff0c;双击下载好的安装文件&#xff0c;选择安装路径安…

继续完善wsl相关内容:基础指令

文章目录 前言一、我们需要安装wsl,这也是安装docker desktop的前提,因此我们在这篇文章里做了介绍:二、虽然我们在以安装docker desktop为目的时,不需要安装wsl的分发(distribution),但是装一个分发也是有诸多好处的:三、在使用wsl时,不建议把东西直接放到系统里,因…

20241124 Typecho 视频插入插件

博文免不了涉及到视频插入这些,网上的插件都或多或少的比较重,和Typecho的风格不搭配 后面就有了DPlay插件精简而来的VideoInsertion插件 VideoInsertion: Typecho 视频插入插件 目录结构 rockhinlink-ht2:/var/www/html/typecho/usr/plugins/VideoInsertion$ tree -h [4.…

css:项目

这是一个完整的网站制作的流程 美工会先制作一个原型图&#xff1a; 原型图写的不详细&#xff0c;就是体现一个网页大致的布局 然后美工再做一个psd样例图片 然后再交给程序员 项目 模块化开发&#xff1a;把代码的不同的样式封装起来&#xff0c;需要用到相同样式的标签就…

Qt桌面应用开发 第九天(综合项目一 飞翔的鸟)

目录 1.鸟类创建 2.鸟动画实现 3.鼠标拖拽 4.自动移动 5.右键菜单 6.窗口透明化 项目需求&#xff1a; 实现思路&#xff1a; 创建项目导入资源鸟类创建鸟动画实现鼠标拖拽实现自动移动右键菜单窗口透明化 1.鸟类创建 ①鸟类中包含鸟图片、鸟图片的最小值下标和最大值…

网络安全期末复习

第1章 网络安全概括 &#xff08;1&#xff09;用户模式切换到系统配置模式&#xff08;enable&#xff09;。 &#xff08;2&#xff09;显示当前位置的设置信息&#xff0c;很方便了解系统设置&#xff08;show running-config&#xff09;。 &#xff08;3&#xff09;显…

使用Python实现自动化邮件通知:当长时程序运行结束时

使用Python实现自动化邮件通知&#xff1a;当长时程序运行结束时 前提声明 本代码仅供学习和研究使用&#xff0c;不得用于商业用途。请确保在合法合规的前提下使用本代码。 目录 引言项目背景项目设置代码分析 导入所需模块定义邮件发送函数发送邮件 实现步骤结语全部代码…

Python学习35天

# 定义父类 class Computer: CPUNone MemoryNone diskNone def __init__(self,CPU,Memory,disk): self.disk disk self.Memory Memory self.CPU CPU def get_details(self): return f"CPU:{self.CPU}\tdisk:{self.disk}\t…