【C++进阶篇】像传承家族宝藏一样理解C++继承
文章目录
须知
💬 欢迎讨论:如果你在学习过程中有任何问题或想法,欢迎在评论区留言,我们一起交流学习。你的支持是我继续创作的动力!
👍 点赞、收藏与分享:觉得这篇文章对你有帮助吗?别忘了点赞、收藏并分享给更多的小伙伴哦!你们的支持是我不断进步的动力!
🚀 分享给更多人:如果你觉得这篇文章对你有帮助,欢迎分享给更多对C++感兴趣的朋友,让我们一起进步!
《深入剖析C++继承:从基础到进阶的完整指南》
1. C++继承前言与背景
1.1 前言
C++是一个功能强大的面向对象编程语言,它不仅支持过程式编程,还在此基础上提供了许多用于构建复杂软件系统的面向对象特性。继承是C++中最为核心的概念之一,它允许我们通过现有的类(基类)创建新的类(派生类),从而实现代码的重用和扩展。继承是面向对象编程的三个基本特性之一(另外两个是封装和多态),在设计模式、软件架构和大型系统开发中起着至关重要的作用。
理解和应用C++继承的概念对于编写高效、可维护和可扩展的代码至关重要。C++的继承不仅仅是一个简单的“类与类之间的关系”,它涉及到如何组织和管理对象之间的共享数据、方法以及如何利用多态实现代码的灵活性。因此,C++继承的深入理解对程序员来说是必须的,它能够帮助开发者更好地设计类的层次结构,提升软件系统的复用性和扩展性。
1.2 背景
继承源于面向对象编程的基本思想,即通过创建类的层次结构,模拟现实世界中的事物关系。在现实世界中,物体之间往往存在父子关系、包含关系、继承关系等。C++的继承机制正是通过类与类之间的继承关系来模拟这些现实中的关系。继承使得开发者能够从一个基类派生出多个派生类,从而共享基类的行为,并在需要时对其进行扩展或修改。
在C++中,继承是通过class
关键字和访问修饰符(如public
、protected
、private
)来实现的,基类(父类)提供了一些公有和保护成员,派生类(子类)可以继承这些成员。继承还允许派生类重写基类的方法(方法重写),并能够通过虚函数实现运行时多态性,这是C++继承特性的重要组成部分。
C++继承的强大之处在于它不仅仅支持单一继承,还支持多继承,这使得它可以适应更复杂的类关系。通过使用虚拟继承,C++避免了传统多继承中可能出现的“钻石继承”问题。此外,C++继承支持访问控制(如public
、protected
和private
继承),从而为开发者提供了灵活的类设计和组织结构的能力。
然而,C++继承的设计和使用也存在一些挑战,特别是在多继承和虚继承的场景下。理解如何合理使用继承关系,避免继承层次过深,避免继承滥用,是程序员需要掌握的关键技能。
C++继承的关键要点:
- 代码重用:继承使得子类能够复用父类的属性和方法,减少重复代码。
- 扩展性:通过继承,子类可以扩展或修改父类的行为,从而实现系统的扩展。
- 多态性:继承和虚函数的结合使得C++能够实现运行时多态,从而使代码更加灵活和动态。
- 多继承与虚继承:C++支持多继承和虚继承,这为开发者提供了强大的功能,但也增加了代码设计的复杂度。
- 访问控制:C++提供了不同的继承访问权限(
public
、protected
、private
),允许开发者控制基类成员的访问权限,确保程序设计的封装性和安全性。
2.引言:C++继承的核心意义
继承是面向对象编程的一个关键特性,它能够使得代码更加简洁、可扩展和易维护。在C++中,继承不仅仅是类之间的关系,更是构建复杂系统的基石。通过继承,我们可以在一个类中共享另一个类的功能,而不需要重复编写相同的代码。
在这篇博客中,我们将深入探讨C++中的继承,包括其基础概念、应用场景、常见问题以及一些进阶技巧。通过示例和图示,您将能够更好地理解继承的各个方面,并能够在项目中有效运用。
3.继承基本概念与定义
3.1 什么是C++继承——从基本概念开始
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 子类名 : 继承方式 基类名 {
// 子类的成员
};
继承语法:
- 使用
public
、private
或protected
访问权限来决定继承的可访问性。public
继承:基类的公有成员在派生类中仍然是公有的。protected
继承:基类的公有成员在派生类中变为受保护的。private
继承:基类的公有成员在派生类中变为私有的。
示例代码:
class Teacher : public Person {
protected:int _jobid; // 工号
};int main() {Student s;Teacher t;s.Print();t.Print();return 0;
}
派生类Student和Teacher都继承基类(父类)Person类的成员方法函数Print(),通过s.Print()和t.Print()输出 Student
和 Teacher
对象的姓名和年龄。
4. 继承中的访问权限
4.1 基类成员在派生类中的访问权限
基类的 public
、protected
和 private
成员在派生类中的访问权限取决于继承方式。下面是不同继承方式下的访问权限表:
从表中可以看出基类的private成员在派生类(子类)始终不可见,而基类的public成员和protected成员的是否能被访问取决于本身成员的访问权限与继承方式,两个取继承方式最坎坷的一个。
注意:1-> 如果需要基类的某个成员在派生类中可访问但不希望类外部访问,则可以将其设置为 protected
,这样可以更好地控制访问权限。
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_cast
、static_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()
,从而确保派生类资源得到正确释放。
总结:
- 虚析构函数:在继承体系中,基类的析构函数应当声明为虚函数,以确保派生类的析构函数能够在删除基类指针时被正确调用。
- 内存泄漏:如果基类的析构函数不是虚函数,那么派生类的析构函数不会被调用,可能会导致资源没有得到正确释放,从而引发内存泄漏。
最后
-
继承是面向对象编程的基础:继承允许通过基类创建派生类,实现代码重用、扩展和模块化设计,是面向对象编程的核心特性之一。
-
增强代码复用与扩展性:通过继承,派生类可以复用基类的代码,同时根据需要扩展或修改功能,提高代码的复用性和系统的灵活性。
-
虚继承解决多继承问题:C++支持多继承,但为了避免多继承中出现的“钻石继承”问题,虚继承机制确保基类的成员只会被继承一次,避免二义性和资源冲突。
-
虚函数和多态性实现动态行为:通过虚函数和多态性,C++使得基类指针或引用可以动态地调用派生类的实现,提高了代码的灵活性和可扩展性。
-
方法重写与函数隐藏的区别:方法重写是通过虚函数实现的多态性,而函数隐藏则是子类中定义的同名函数覆盖了父类中的函数,但不支持多态性。
-
析构函数必须为虚函数:当基类指针指向派生类对象时,析构函数必须声明为虚函数,以确保派生类的资源能够被正确释放,避免内存泄漏。
-
继承设计的最佳实践:继承应遵循简洁、清晰的设计原则,避免过深的继承层次和滥用多继承,确保类之间的关系符合“里氏替换原则”,从而提高代码的可维护性和可扩展性。
总结而言,C++继承是实现高效、灵活、可扩展的软件系统的核心工具,但继承的设计与使用应遵循一定的原则,避免复杂性和误用,从而提升代码质量和系统的可维护性。
路虽远,行则将至;事虽难,做则必成
亲爱的读者们,下一篇文章再会!!!
相关文章:
【C++进阶篇】像传承家族宝藏一样理解C++继承
文章目录 须知 💬 欢迎讨论:如果你在学习过程中有任何问题或想法,欢迎在评论区留言,我们一起交流学习。你的支持是我继续创作的动力! 👍 点赞、收藏与分享:觉得这篇文章对你有帮助吗࿱…...
Java基础面试题09:Java异常处理完成以后,Exception对象会发生什么变化?
一、Java异常(Exception)基本概念 什么是异常? 简单来说,异常就是程序运行时发生了意外的“错误”或者“不正常现象”,导致程序中断。异常处理的目标是让程序在出现问题时能稳住,不会直接崩溃。 1.1 异常…...
mysql sql语句 between and 是否边界值
在 MySQL 中,使用 BETWEEN 运算符时,边界值是包括在内的。这意味着 BETWEEN A AND B 查询会返回 A 和 B 之间的所有值,包括 A 和 B 自身。 示例 假设有一个表 employees,其中有一个 salary 列,您可以使用以下查询&am…...
Java接收LocalDateTime、LocalDatee参数
文章目录 引言I java服务端的实现1.1 基于注解规范日期格式1.2 json序列化和反序列化全局配置自动处理日期格式化II 知识扩展: 枚举的转换和序列化III 签名注意事项引言 应用场景举例:根据时间段进行分页查询数据 前后端交互日期字符串统一是yyyy-MM-dd HH:mm:ss 或者yyyy-M…...
方差分析、相关分析、回归分析
第一章:方差分析 1.1 方差分析概述 作用: 找出关键影响因素,并进行对比分析,选择最佳组合方案。影响因素: 控制因素(人为可控)和随机因素(人为难控)。控制变量的不同水平: 控制变量的不同取值…...
SQLModel入门
SQLModel 系统性指南 目录 简介 什么是 SQLModel?为什么使用 SQLModel? 安装快速入门 定义模型创建数据库和表 基本 CRUD 操作 创建(Create)读取(Read)更新(Update)删除࿰…...
单片机蓝牙手机 APP
目录 一、引言 二、单片机连接蓝牙手机 APP 的方法 1. 所需工具 2. 具体步骤 三、单片机蓝牙手机 APP 的应用案例 1. STM32 蓝牙遥控小车 2. 手机 APP 控制 stm32 单片机待机与唤醒 3. 智能家居系统 4. 智能记忆汽车按摩座椅 四、单片机蓝牙手机 APP 的功能 1. 多种控…...
PostgreSQL在Linux环境下的常用命令总结
标题 登录PgSQL库表基本操作命令新建库表修改库表修改数据库名称:修改表名称修改表字段信息 删除库表pgsql删除正在使用的数据库 须知: 以下所有命令我都在Linux环境中执行验证过,大家放心食用,其中的实际名称换成自己的实际名称即…...
Unity shaderlab 实现LineSDF
实现效果: 实现代码: Shader "Custom/LineSDF" {Properties{}SubShader{Tags { "RenderType""Opaque" }Pass{CGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"struct appdata{floa…...
Ubuntu中的apt update 和 apt upgrade
apt update 和 apt upgrade 是 Debian 及其衍生发行版(如 Ubuntu)中常用的两个 APT 包管理命令,它们各自执行不同的任务: apt update: 这个命令用于更新本地软件包列表。当你运行 apt update 时,APT 会从配置的源&…...
Android 中 Swipe、Scroll 和 Fling 的区别
Android 中 Swipe、Scroll 和 Fling 的区别 Swipe(滑动)Scroll(滚动)Fling(甩动)三者之间的区别代码示例 (Fling)总结 在 Android 应用中,Swipe、Scroll 和 Fling 都是用户在触摸屏幕上进行的滑…...
linux基础2
声明! 学习视频来自B站up主 泷羽sec 有兴趣的师傅可以关注一下,如涉及侵权马上删除文章,笔记只是方便各位师傅的学习和探讨,文章所提到的网站以及内容,只做学习交流,其他均与本人以及泷羽sec团队无关&#…...
如何通过智能生成PPT,让演示文稿更高效、更精彩?
在快节奏的工作和生活中,我们总是追求更高效、更精准的解决方案。而在准备演示文稿时,PPT的制作往往成为许多人头疼的问题。如何让这项工作变得轻松且富有创意?答案或许就在于“AI生成PPT”这一智能工具的广泛应用。我们就来聊聊如何通过这些…...
执法记录仪数据自动备份光盘刻录归档系统
派美雅按需研发的执法记录仪数据自动备份光盘刻录归档系统,为用户提供数据自动上传到刻录服务端、数据上传后自动归类,全自动对刻录端视频文件大小进行实时监测,满盘触发刻录,无需人工干预。告别传统刻录存在的痛点,实…...
启动SpringBoot
前言:大家好我是小帅,今天我们来学习SpringBoot 文章目录 1. 环境准备2. Maven2.1 什么是Maven2.2 创建⼀个Maven项⽬2.3 依赖管理2.3.1 依赖配置2.3.2 依赖传递2.3.4 依赖排除2.3.5 Maven Help插件(plugin) 2.4 Maven 仓库2.6 中…...
重定向操作和不同脚本的互相调用
文章目录 前言重定向操作和不同脚本的互相调用 前言 声明 学习视频来自B站UP主 泷羽sec,如涉及侵权马上删除文章 笔记的只是方便各位师傅学习知识,以下网站只涉及学习内容,其他的都与本人无关,切莫逾越法律红线,否则后果自负 重定向操作和不同脚本的互相调用 1.不同脚本的互相…...
51单片机教程(九)- 数码管的动态显示
1、项目分析 通过演示数码管动态显示的操作过程。 2、技术准备 1、 数码管动态显示 4个1位数码管和单片机如何连接 a、静态显示的连接方式 优点:不需要动态刷新;缺点:占用IO口线多。 b、动态显示的连接方式 连接:所有位数码…...
golang支持线程安全和自动过期map
在 Golang 中,原生的 map 类型并不支持并发安全,也没有内置的键过期机制。不过,有一些社区提供的库和方案可以满足这两个需求:线程安全和键过期。 1. 使用 sync.Map(线程安全,但不支持过期) Go…...
机器学习之RLHF(人类反馈强化学习)
RLHF(Reinforcement Learning with Human Feedback,基于人类反馈的强化学习) 是一种结合人类反馈和强化学习(RL)技术的算法,旨在通过人类的评价和偏好优化智能体的行为,使其更符合人类期望。这种方法近年来在大规模语言模型(如 OpenAI 的 GPT 系列)训练中取得了显著成…...
泷羽sec---shell作业
作业一 写计算器 使用bc命令 需要进行安装bc 代码如下: #!/bin/bash echo "-----------------------------------" echo "输入 f 退出" echo "可计算小数和整数" echo "用法如:1.12.2" echo "------…...
华为海思2025届校招笔试面试经验分享
目前如果秋招还没有offer的同学,可以赶紧投递下面这些公司,都在补招。争取大家年前就把后端offer拿下。如果大家在准备秋招补录取过程中有任何问题,都可以私信小编,免费提供帮助。如果还有部分准备备战春招的同学,也可…...
摆脱复杂配置!使用MusicGPT部署你的私人AI音乐生成环境
文章目录 前言1. 本地部署2. 使用方法介绍3. 内网穿透工具下载安装4. 配置公网地址5. 配置固定公网地址 前言 今天给大家分享一个超酷的技能:如何在你的Windows电脑上快速部署一款文字生成音乐的AI创作服务——MusicGPT,并且通过cpolar内网穿透工具&…...
嵌入式Linux中的GPIO编程
GPIO(General Purpose Input Output)是嵌入式系统中非常常见的一种硬件资源,它允许开发者直接控制微处理器或微控制器的引脚。通过设置这些引脚的状态,可以实现对硬件设备的控制,如LED灯的开关、传感器数据的读取等。 …...
js:函数
函数 函数:实现抽取封装,执行特定任务的代码块,方便复用 声明 函数命名规范 尽量小驼峰 前缀应该为动词,如getName、hasName 函数的调用 函数体是函数的构成部分 函数传参 参数列表里的参数叫形参,实际上写的数据叫实…...
低代码平台审批流程设计
审批流程设计 在此界面设置审批单从发起、到审批、再到结束的流转步骤。 6.1 添加节点 点击两个节点间连线的 图标可添加 审批人、抄送人、办理人、条件分支。 6.2 节点类型 提交节点 点击提交节点,可在右侧弹窗中设置提交节点的抄送人,实现审批在发…...
OpenCV相机标定与3D重建(8)相机标定函数calibrateCamera()的使用
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 从校准图案的多个视图中找到相机的内参和外参参数. cv::calibrateCamera 是 OpenCV 中用于相机标定的一个非常重要的函数。它通过一系列已知的世…...
Linux信号量的编程
一,用信号量来实现是父进程先进行,还是子进程先进性 信号量的没有P,V操作之前,我们不知道如何控制: #include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h>…...
“Yaker,你可以全局配置插件环境变量!“
周四周四,Vme50(bushi 大家好,这里是疯狂超级牛(功能上新版) 经常有用户问 “牛牛如何为不同插件配置相同的变量值呢?” “能有一个一波搞定插件变量的方式就好了” 超级牛听到了广大用户的声音,默默地拿起…...
SAAS美容美发系统架构解析
随着技术的不断发展,SAAS(Software as a Service,软件即服务)模式在各个行业的应用逐渐深化,美容美发行业也不例外。传统的美容美发店面通常依赖纸质记录、手动操作和复杂的管理流程,而随着SAAS平台的出现&…...
如何为 ext2/ext3/ext4 文件系统的 /dev/centos/root 增加 800G 空间
如何为 ext2/ext3/ext4 文件系统的 /dev/centos/root 增加 800G 空间 一、引言二、检查当前磁盘和分区状态1. 使用 `df` 命令检查磁盘使用情况2. 使用 `lsblk` 命令查看分区结构3. 使用 `fdisk` 或 `parted` 命令查看详细的分区信息三、扩展逻辑卷(如果使用 LVM)1. 检查 LVM …...
网站建设网站建/百度竞价推广代运营公司
背景描述 EasyNVR的使用者应该都是清楚的了解到,EasyNVR一个强大的功能就是可以进行全平台的无插件直播。主要原因在于rtsp协议的视频流(默认是需要插件才可以播放的)经由EasyNVR处理可以满足无插件的全平台直播。 经由EasyNVR处理会获取到RT…...
如何做微网站平台/百度世界排名
ios操作系统的流畅度非常的高,因此人们都愿意购买 苹果 的手机。大家都知道苹果智能手机的售价是非常昂贵的,并不是社会上每一个人都可以负担得起的。有非常多的消费者为了可以达到非常好的操作体验,因此有非常多人们都会购买安卓智能手机&am…...
wordpress用什么主机/公司网站建设哪个好
前言 最近好像遇到了黑客,数据库总是被删,然后我在数据库上加了权限,禁止使用drop命令,结果把自己也限制住了,自己新建了一个表,却删除不了。下面这个方法可以跨过用户权限使用drop命令,同时也…...
soho外贸网站建设/优化seo是什么意思
文章目录题目题目解析解题代码我的个人小站: acking-you.github.io题目 OJ平台 题目解析 实际上就是一个前缀和二分的处理,我一旦爆出前缀和二分,应该就都有思路了! 解题代码 这里偷懒使用了STL,当然也可自己去写二…...
上海特种作业操作证查询/seo运营是什么意思
一、Widget设计步骤 需要修改三个XML,一个class:1.第一个xml是布局XML文件(如:main.xml),是这个widget的。一般来说如果用这个部件显示时间,那就只在这个布局XML中声明一个textview就OK了。2.第二个xml是widget_pro…...
网站开发案例/外链查询
1. 实现方法 处理器有一个24位的系统定时器,SysTick。该定时器向下计数。计数到0后,重新从STK_LOAD寄存器中重载计数值,继续向下计数。使用SysTick的优点是,不用占用中断和定时器资源。 2. 相关寄存器 STK_CTRL: 0…...