【C++】---继承

【C++】---继承

  • 一、继承的概念及定义
    • 1、继承的概念
    • 2、定义语法格式
    • 3、继承基类成员访问方式的变化
  • 二、基类 和 派生类 的对象之间的赋值转换
    • 1、赋值规则
    • 2、切片
      • (1)子类对象 赋值 给 父类对象
      • (2)子类对象 赋值 给 父类指针
      • (3)子类对象 赋值 给 父类引用
  • 三、继承中的作用域
    • 1、父类和子类都有独立的作用域
    • 2、隐藏的概念
    • 3、如果是成员函数的隐藏,只需要函数名相同 就构成 隐藏
    • 4、继承中最好不要定义同名成员
  • 四、派生类的默认成员函数
    • 1、规则①
    • 2、规则②
    • 3、规则③
    • 4、规则④
  • 五、继承和友元
  • 六、继承和静态成员
  • 七、菱形继承
    • 1、单继承
    • 2、多继承
    • 3、菱形继承
  • 八、菱形虚拟继承
    • 1、虚继承
    • 2、虚继承的解决数据冗余 和 二义性的原理
  • 九、继承和组合

一、继承的概念及定义

1、继承的概念

继承的概念:

继承机制是面向对象程序设计当中使代码复用的重要手段,它允许程序员,在保持原有类特性的基础上,进行拓展,增加功能,产生新的类,这个新的类就叫做:派生类。

继承的出现,呈现出面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。

以前我们所接触到的复用都是函数的复用,而继承的出现则是体现了:类设计层次的复用。
在这里插入图片描述

2、定义语法格式

在这里插入图片描述

#include<iostream>
using namespace std;// 基类
class Person
{
public:void Print(){cout << "name->" << _name << endl;cout << "age->" << _age << endl;}
protected:string _name = "peter";int _age = 18;
};// 派生类:stu
class Student :public Person
{
protected:int stu_ID;
};
// 派生类:tea
class Teacher :public Person
{
protected:int job_ID;
};int main()
{Student s;Teacher t;s.Print();t.Print();return  0;
}

3、继承基类成员访问方式的变化

在这里插入图片描述
不需要记住总结上面的表
我给大家总结两点,只需要记住下面两点规律,一切都好说:

(1)基类private成员在派生类中无论以什么方式继承都是不可见的

(2)基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),访问权限,由大到小:public>protected>private。(Min(成员在基类的访问限定符,继承方式):意思是 取 两者访问权限较小的!)


注意:基类private成员在派生类中无论以什么方式继承都是不可见的

关于什么叫做不可见:

假如将父类成员改为private,那么子类继承了父类的私有成员,内存上,子类对象有这个成员,但是语法上不允许访问
可以理解为父类就是你的父亲,而父类的私有成员就是你父亲藏的私房钱。子类就是你自己,不可见的意思,就是说:你知道你父亲藏有私房钱,但是你找不到你见不到!

二、基类 和 派生类 的对象之间的赋值转换

1、赋值规则

(1)派生类对象可以赋值给:基类对象 / 基类的指针 / 基类的引用。这里有一个形象的说法叫做切割或者切片,就是将派生类中父类那一部分切割下来,赋值过去。(子可以给父)

(2)基类对象不能赋值给派生类对象。(父不能给子)

(3)基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(RunTimeType Information)的dynamic_cast 来进行识别后进行安全转换。(ps:这个我们后面再讲解,这里先了解一下)

2、切片

(1)子类对象 赋值 给 父类对象

子可以赋值给父,但 父不可以赋值给子:
在这里插入图片描述

// 基类
class Person
{
public:void Print(){cout << "name->" << _name << endl;cout << "age->" << _age << endl;}string _name = "peter";int _age = 18;
};// 派生类:stu
class Student :public Person
{
public:int stu_ID;
};int main()
{Student s;// s是一个 子类对象Person p;// p是一个 父类对象s._name = "yjl";s._age = 20;s.stu_ID = 20221071;p = s;s = p;return  0;
}

在这里插入图片描述

(2)子类对象 赋值 给 父类指针

在这里插入图片描述

① 子类对象可以赋值给父类指针


// 基类
class Person
{
public:void Print(){cout << "name->" << _name << endl;cout << "age->" << _age << endl;}string _name = "peter";int _age = 18;
};// 派生类:stu
class Student :public Person
{
public:int stu_ID;
};int main()
{Student s;// s是一个 子类对象//Person p;// p是一个 父类对象Person* p;// p是一个 父类对象指针s._name = "yjl";s._age = 20;s.stu_ID = 20221071;p = &s;return  0;
}

在这里插入图片描述
② 若父类指针要赋值给子类指针,必须将父类指针强转为子类指针:

int main()
{//父类指针赋值给子类指针Person p;//父类对象Student *s;//子类指针p._name = "yjl";p._sex = "male";p._age = 6;Person* pp = &p;//父类指针s = (Student*)pp;//将父类指针强转为子类型指针后,再赋值给子类型指针return 0;
}

③ 指向父类对象的父类指针,强转为子类指针后赋值给子类指针可能会造成访问越界

int main()
{//指向父类对象的父类指针,强转为子类指针后赋值给子类指针可能会造成访问越界Person p;p._name = "yjl";p._sex = "male";p._age = 7;//s._No = 21530;Person* pp = &p;Student* ss = (Student*)pp;//ss->_No = 6;访问越界造成程序崩掉return 0;
}

(3)子类对象 赋值 给 父类引用

// 基类
class Person
{
public:void Print(){cout << "name->" << _name << endl;cout << "age->" << _age << endl;}string _name = "peter";int _age = 18;
};// 派生类:stu
class Student :public Person
{
public:int stu_ID;
};int main()
{Student s;// s是一个 子类对象//Person p;// p是一个 父类对象//Person* p;// p是一个 父类对象指针s._name = "yjl";s._age = 20;s.stu_ID = 20221071;Person& p=s;// p是一个 父类对象引用return  0;
}

在这里插入图片描述

三、继承中的作用域

1、父类和子类都有独立的作用域

2、隐藏的概念

(1)子类和父类中有同名成员,子类成员将屏蔽父类,对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)

(2)通过代码来演示:隐藏!

class Person
{
public:string _name = "yjl";int _age = 20;void Print1(){cout << "name->" << _name << endl;cout << "age->" << _age << endl;}
};class Student :public Person
{
public:int _age = 100;void Print2(){cout << "age->" << _age << endl;}
};int main()
{Person p;Student s;s.Print2();return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3、如果是成员函数的隐藏,只需要函数名相同 就构成 隐藏

// 父类
class Person
{
public:string _name = "yjl";int _age1 = 20;void Print(){cout << "name->" << _name << endl;cout << "age->" << _age1 << endl;}
};
// 子类
class Student :public Person
{
public:int _age2 = 100;void Print(int count){while (count){cout << "age->" << _age2 << endl;count--;}}
};
int main()
{Person p;Student s;// 这里的s.Print();与父类的Print  构成函数重载×××,构成函数隐藏√√√// 父子类函数构成隐藏,只需要满足一点即可(函数名相同)s.Print();// 这里认为你用子类对象调用的是子类的打印函数,而不是你想要调用的父类打印函数。return 0;
}

在这里插入图片描述

4、继承中最好不要定义同名成员

四、派生类的默认成员函数

(1)什么叫做默认成员函数?

6个默认成员函数,“默认”的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类中,这几个成员函数是如何生成的呢?

(2)派生类的默认成员函数和普通类的默认成员函数的不同区别:

  1. 派生类的构造函数必须要调用基类的构造函数来初始化基类的那一部分成员。如果基类没有写默认的构造函数,那么就必须要在派生类的初始化列表里面对基类的构造函数进行显示调用。(构造:先父后子)
  2. 派生类的拷贝构造函数必须要调用基类的拷贝构造函数,来进行对基类成员的拷贝构造。
  3. 派生类的operator=必须要调用基类的operator=完成基类的赋值
  4. 派生类的析构函数,会在被调用完成之后,自动调用基类的析构函数来清理基类的成员。因为这样才能保证析构函数作用的时候是先析构子类的成员,最后再析构父类的成员的顺序(析构:先子后父)
  5. 因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同(这个我们后面会讲解)。那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加显示调用的情况下,子类析构函数和父类析构函数构成隐藏关系。

总之:派生类=基类+派生类成员(①把基类看作是一个自定义类型的整体->无论是构造机构,拷贝构造,基类就调用自己的默认写的!②派生类成员看作一个普通的类!)

// 父类
class Person
{
public: 构造//Person(const char* name = "peter")//	:_name(name)//{//	cout << "Person()" << endl;//} 拷贝构造//Person(const Person& p)//	:_name(p._name)//{//	cout << "Person(const Person& p)" << endl;//}// 析构~Person(){cout << "~Person()" << endl;}// 赋值运算符重载Person& operator=(const Person& p){cout << "Person& operator=()" << endl;if (this != &p){_name = p._name;}return *this;}
protected:string _name;
};// 子类
class Student :public Person
{
public:// 构造Student(const char* name,int num):Person(),_num(num){cout << "Student()" << endl;}// 拷贝构造Student(const Student& s):Person(s), _num(s._num){cout << "Student(const Student& s)" << endl;}// 赋值运算符重载Student& operator=(const Student& s){cout << "Student& operator=" << endl;if (this != &s){Person::operator=(s);_num = s._num;}return *this;}// 析构~Student(){cout << "~Student()" << endl;}
protected:int _num;// 学号
};void test()
{Person p1;Student s1("yjl", 2022);//Student s2("yjl666", 2024);//Student s3(s1);//Person p2(s3);}int main()
{test();return 0;
}

1、规则①

规则①:子类的构造函数必须调用父类的构造函数初始化父类的那一部分成员。如果父类没有默认的构造函数,则必须在子类构造函数的初始化列表阶段显式调用。

(1)回忆一下什么是默认构造函数?

  1. 我们没有写编译器,自动生成的。
  2. 我们自己写了无参的构造函数。
  3. 我们自己写了的,带参的全缺省构造函数。

(2)父类里面没有写默认的构造函数和拷贝构造函数:
在这里插入图片描述
当父类没有写默认的构造函数的时候,显式调用父类默认构造函数,是把当作一个整体去调父类的构造函数!
(3)父类里面写了默认的构造函数:

在这里插入图片描述
总结:
(1)父类有默认构造函数,子类只需要在构造函数中初始化子类自己的成员
(2)父类没有默认构造函数,子类要在构造函数初始化列表显式调用父类的构造函数,再初始化子类自己的成员

2、规则②

规则②:子类的拷贝构造函数必须调用父类的拷贝构造完成父类的拷贝初始化。

子类的拷贝构造函数该如何写呢?

调用父类拷贝构造函数时,将子类对象作为参数传给父类拷贝构造函数,这时会发生切片行为,将子类对象中父类成员部分切片赋值给父类成员,完成父类拷贝构造;再将子类自己的成员初始化为拷贝对象的子类成员值:

3、规则③

规则③ 子类的operator=必须要调用父类的operator=完成父类的赋值

如果子类的赋值运算符重载函数里只调用子类的operator=,那么会发生栈溢出:

// 赋值运算符重载Student& operator=(const Student& s){cout << "Student& operator=" << endl;if (this != &s){operator=(s);_num = s._num;}return *this;}void test()
{//Person p1;Student s1("yjl", 2022);Student s2("yjl666", 2024);s1 = s2;// 赋值的时候,会发生:栈溢出//Person p2(s3);}

在这里插入图片描述
这样写才对:

	// 赋值运算符重载Student& operator=(const Student& s){cout << "Student& operator=" << endl;if (this != &s){Person::operator=(s);_num = s._num;}return *this;}

4、规则④

规则④ 子类的析构函数会在被调用完成后自动调用父类的析构函数清理父类成员。因为这样才能保证子类对象先清理子类成员再清理父类成员的顺序
1、
(1)析构顺序:先子后父!

(2)构造顺序:先父后子!

在这里插入图片描述
2、注意:由于多态的缘故,对析构的统一处理

// 对析构的统一处理:~Student(){//Person::~Person();~Person();cout << "~Student()" << endl;}

在这里插入图片描述
这是由于父类的析构函数和子类的析构函数构成隐藏,奇不奇怪?

按照隐藏的定义,父类和子类的同名函数才会构成隐藏,为什么父类和子类的析构函数不同名也会构成隐藏呢?

这是因为多态的缘故,任何类的析构函数名都会被统一处理成destructor( )。所以父类和子类析构函数同名,会构成隐藏。解决方法就是加父类作用域,指定访问的是父类的析构函数就可以了:

	// 析构~Student(){Person::~Person();//~Person();cout << "~Student()" << endl;}

在这里插入图片描述

五、继承和友元

友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员

class Student;
class Person
{
public:friend void Display(const Person& p, const Student& s);
protected:string _name; // 姓名
};
class Student : public Person
{
protected:int _stuNum; // 学号
};
void Display(const Person& p, const Student& s)
{cout << p._name << endl;cout << s._stuNum << endl;
}
void main()
{Person p;Student s;Display(p, s);
}

六、继承和静态成员

如果在父类里面定义了一个static静态成员,那么在整个继承体系当中,无论父类派生出多少个子类,整个体系只有这一个静态成员!

下面这些代码是对静态成员变量的诠释,在整个继承体系中,只要有static静态成员变量,那么它就是同一个!

#include<iostream>using namespace std;class Person
{
public:Person() { _count++; }
protected:string _name;
public:static int _count;
};int Person::_count = 0;class Student : public Person
{
protected:int _ID;
};class Graduate : public Student
{
protected:string _course;
};void TestPerson()
{Student s1;Student s2;Student s3;Graduate s4;// 在上面的继承体系中,它是单继承。// 创建子类对象的时候必须要先构造父类对象,所以进行到此处_count等于4。cout << "人数:" << Person::_count << endl;Student::_count = 0;// 因为已经说明了_count是一个静态的,在整个继承体系中它就是一个整体就是一个// 对_count修改后他就是0了。cout << "人数:" << Person::_count << endl;
}int main()
{TestPerson();return 0;
}

在这里插入图片描述

七、菱形继承

1、单继承

单继承:一个子类只有一个直接父类时称这个继承关系为单继承

在这里插入图片描述

2、多继承

多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承

在这里插入图片描述

3、菱形继承

菱形继承:菱形继承是多继承的一种特殊情况。

在这里插入图片描述

class Person
{
public:string _name;
};class Student : public Person
{
protected:int _num;
};class Teacher : public Person
{
protected:int _id;
};class Assistant :public Student, public Teacher
{
protected:string _course;
};void Test()
{Assistant a;a._name = "peter";// 这样会有二义性无法明确知道访问的是哪一个_name// 因为,a访问的_name没有指明是student继承person的_name,还是teacher继承person的_name。// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决a.Student::_name = "xxx";a.Teacher::_name = "xxx";
}

菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。
在Assistant的对象中Person成员会有两份。

在这里插入图片描述

八、菱形虚拟继承

虚拟继承:可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和Teacher的继承Person时使用虚拟继承,即可解决问题。
需要注意的是,虚拟继承不要在其他地方去使用。

1、虚继承

虚继承的语法格式:在两个直接父类的继承方式访问限定符前加vitual:

class Person
{
public:string _name;
};class Student : virtual public Person
{
protected:int _num;
};class Teacher : virtual public Person
{
protected:int _id;
};class Assistant :public Student, public Teacher
{
protected:string _course;
};void Test()
{Assistant a;a._name = "peter";/*a.Student::_name = "xxx";a.Teacher::_name = "xxx";*/
}int main()
{Test();return 0;
}

在这里插入图片描述

2、虚继承的解决数据冗余 和 二义性的原理

虚拟继承解决数据冗余和二义性的原理:

为了研究虚拟继承原理,我们给出了一个简化的菱形继承继承体系,再借助内存窗口观察对象成员的模型

class A
{
public:int _a;
};
// class B : public A
class B : virtual public A
{
public:int _b;
};
// class C : public A
class C : virtual public A
{
public:int _c;
};
class D : public B, public C
{
public:int _d;
};
int main()
{D d;d.B::_a = 1;d.C::_a = 2;d._b = 3;d._c = 4;d._d = 5;return 0;
}

下图是菱形继承的内存对象成员模型:这里可以看到数据冗余:
在这里插入图片描述

下图是菱形虚拟继承的内存对象成员模型:这里可以分析出D对象中将A放到的了对象组成的最下面,这个A同时属于B和C,那么B和C如何去找到公共的A呢?这里是通过了B和C的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的A。

在这里插入图片描述
下面是上面的Person关系菱形虚拟继承的原理解释:

在这里插入图片描述

九、继承和组合

  1. 很多人说C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承。否则在复杂度及性能上都有问题。
  2. 多继承可以认为是C++的缺陷之一,很多后来的OO语言都没有多继承,如Java。
  3. 继承和组合:

(1)public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。

(2)组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。

(3)优先使用对象组合,而不是类继承 :链接:优先使用对象组合,而不是类继承

(4)实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用
继承,可以用组合,就用组合。

// Car和BMW Car和Benz构成is-a的关系class Car{protected:string _colour = "白色"; // 颜色string _num = "陕ABIT00"; // 车牌号};class BMW : public Car{public:void Drive() {cout << "好开-操控" << endl;}};class Benz : public Car{public:void Drive() {cout << "好坐-舒适" << endl;}};// Tire和Car构成has-a的关系class Tire{protected:string _brand = "Michelin";  // 品牌size_t _size = 17;         // 尺寸};class Car{protected:string _colour = "白色"; // 颜色string _num = "陕ABIT00"; // 车牌号Tire _t; // 轮胎};

好了,今天的分享就到这里了
如果对你有帮助,记得点赞👍+关注哦!
我的主页还有其他文章,欢迎学习指点。关注我,让我们一起学习,一起成长吧!

在这里插入图片描述

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

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

相关文章

基于JSP物流信息网的设计与实现(论文 + 源码)

【免费】物流信息网实现.zip资源-CSDN文库https://download.csdn.net/download/JW_559/89267499 基于JSP物流信息网的设计与实现 摘要 本文讲述了基于JSP物流信息网的设计与实现。该系统使用java语言开发&#xff0c;使系统具有更好的平台性和可扩展性。 该系统实现了用户登录、…

英语学习笔记6——What make is it?

What make is it? 它是什么牌子的&#xff1f; make n.&#xff08;产品的&#xff09;品牌名称    v. 制作 区别&#xff1a;model n.&#xff08;产品的&#xff09;型号       n. 模型       n. 模特 make 指的是大的品牌名称&#xff0c; model 是旗下产品…

第2章.STM32开发C语言常用知识点

目录 0. 《STM32单片机自学教程》专栏总纲 2.1. STM32嵌入式开发C语言编程的不同 2.2. C语言常用知识点 2.2.1 位操作 2.2.2 define 宏定义 2.2.3 条件编译 2.2.3.1 #ifdef 2.2.3.2 #ifndef 2.2.3.3 #if !defined 2.2.4 extern 变量声明 2.2.5 typedef 类型别名 …

Sealos急速部署生产用k8s集群

最近一段时间部署k8s全部使用sealos了&#xff0c;整体使用感觉良好&#xff0c;基本没有什么坑。推荐给大家。 使用 Sealos&#xff0c;可以安装一个不包含任何组件的裸 Kubernetes 集群。 最大的好处是提供 99 年证书&#xff0c;用到我跑路是足够了。不用像之前kubeadm安装…

在uniapp中如何安装axios并解决跨域问题

目录 1、安装axios 2、导入 3、使用&#xff08;发请求&#xff09; 2.解决跨域问题 1.为什么要解决跨域问题&#xff1f; 2.前端如何解决跨域问题&#xff1f; 1、安装axios npm install axios 2、导入 在main.js中导入使用 import axios from axios; // 创建一个名…

通过 Java 操作 redis -- hash 哈希表基本命令

目录 使用命令 hset&#xff0c;hget 使用命令 hexists 使用命令 hdel 使用命令 hkeys&#xff0c;hvals 使用命令 hmget&#xff0c;hmset 关于 redis hash 哈希表类型的相关命令推荐看Redis - hash 哈希表 要想通过 Java 操作 redis&#xff0c;首先要连接上 redis 服务…

思通数科大模型在智能数据查询系统中的深度应用:销售数据分析的革新

在企业决策支持系统中&#xff0c;销售数据分析占据着举足轻重的地位。思通数科的大模型技术&#xff0c;结合自然语言处理&#xff08;NLP&#xff09;和机器学习&#xff0c;为智能数据查询系统提供了强大的分析能力。本文将详细描述思通数科大模型在销售数据分析中的应用&am…

固定资产管理系统参考论文(论文 + 源码)

【免费】固定资产管理系统.zip资源-CSDN文库https://download.csdn.net/download/JW_559/89282536 固定资产管理系统 摘 要 随着计算机信息技术的发展以及对资产、设备的管理科学化、合理化的高要求&#xff0c;利用计算机实现设备及资产的信息化管理已经显得非常重要。 固…

渗透之sql注入实战2(二次注入)

目录 平台地址&#xff1a; 开始&#xff1a; 方法1&#xff1a; 方法二 找提示 这里存在一个文件包含&#xff08;file&#xff09;。 爆源码 index.php源码 confirm.php源码&#xff1a; search.php源码&#xff1a; change.php源码&#xff1a; delete.php源码&…

【如何在本地安装Llama 3,坐好30秒!稳的很!!】

如何在本地安装Llama 3&#xff0c;30秒闪速教程 点击 Ollama官网 点击对应系统内容进行下载 解压后打开文件: 双击后打开 复制 打开CMD 窗口终端 复制黏贴 ollama run llama3 等下载完成 OK&#xff0c;安装完成&#xff01; 输入问题&#xff0c;愉快玩耍&#xff01; …

嵌入式学习69-C++(Opencv)

知识零碎&#xff1a; QT的两种编译模式 1.debug 调试模式 …

C++:类与对象—继承

类与对象—继承 一、继承是什么&#xff1f;二、继承定义三、基类和派生类对象赋值转换四、继承中的作用域五、派生类的默认成员函数六、继承与友元七、继承与静态成员八、复杂的菱形继承及菱形虚拟继承九、继承的总结和反思十、考察重点 一、继承是什么&#xff1f; 继承(inh…

​​​【收录 Hello 算法】第 4 章 数组与链表

第 4 章 数组与链表 数据结构的世界如同一堵厚实的砖墙。 数组的砖块整齐排列&#xff0c;逐个紧贴。链表的砖块分散各处&#xff0c;连接的藤蔓自由地穿梭于砖缝之间。 本章内容 4.1 数组4.2 链表4.3 列表4.4 内存与缓存 *4.5 小结

Clion STM32CubeMX 项目

系列文章目录 前言 最后修改 2024 年 4 月 16 日 操作系统&#xff1a;Windows / Linux / macOS 所需工具 STM32CubeMX、GNU ARM 工具链 项目格式&#xff1a; CMake 兼容配置&#xff1a; OpenOCD 运行与调试/嵌入式 GDB 服务器 对于以 STM32 板卡为目标的嵌入式项目&#xf…

小白入门:创建一个SpringBoot项目

前言 我们在创建SpringBoot项目时候&#xff0c;会出现不确定和报错的情况很多&#xff0c;大家可以按照我的做法来简单创建一个SpringBoot项目 1.环境配置 下载安装并配置jdk1.8下载apache mavenidea软件 2.开始创建项目 Server URL&#xff1a;初始是start.spring.io,我…

重写muduo之Thread、EventLoopThread、EventLoopThreadPool

目录 1、概述 2、Thread 2.1 Thread.h 3、EventLoopThread 3.1 EventLoopThread.h 3.2 EventLoopThread.cc 4、 EventLoopThreadPool 4.1 EventLoopThreadPool.h 4.2 EventLoopThreadPool.cc 1、概述 管理事件循环线程的调度的 打包了一个EventLoop和线程&#xff0c;…

JUC下的ScheduledThreadPoolExecutor详解

ScheduledThreadPoolExecutor是Java并发编程框架中一个强大且灵活的线程池实现&#xff0c;专为定时与周期性任务而设计。作为ThreadPoolExecutor的子类&#xff0c;它不仅继承了线程池管理的高效与灵活性&#xff0c;还内置了基于优先级队列的延迟任务调度机制&#xff0c;支持…

0508_IO3

练习1&#xff1a; 1&#xff1a;使用 dup2 实现错误日志功能 使用 write 和 read 实现文件的拷贝功能&#xff0c;注意&#xff0c;代码中所有函数后面&#xff0c;紧跟perror输出错误信息&#xff0c;要求这些错误信息重定向到错误日志 err.txt 中去 1 #include <stdio.h…

掌控网络流量,优化网络性能 - AnaTraf网络流量分析仪登场

在当今日新月异的网络环境中,网络流量监控和性能诊断已成为企业IT部门不可或缺的重要工作。只有充分了解网络流量状况,才能有效优化网络性能,提高业务运营效率。针对这一需求,全新推出的AnaTraf网络流量分析仪应运而生,为企业提供全面的网络监控和性能诊断解决方案。 快速定位…

NodeMCU ESP8266 操作 SSD1306 OLED显示屏详解(图文并茂)

文章目录 1 模块介绍2 接线介绍3 安装SSD1306驱动库4 源码分析4.1 硬件兼容性4.2 可能存在的问题总结1 模块介绍 我们将在本教程中使用的OLED显示屏是SSD1306型号:单色0.96英寸显示屏,像素为12864,如下图所示。 OLED显示屏不需要背光,这在黑暗环境中会产生非常好的对比度。…