【C++】继承详解

news2024/11/13 9:20:54

目录

  • 继承的概念及定义
    • 继承的概念
    • 继承的定义
      • 定义格式
      • 继承关系和访问限定符
      • 继承基类成员访问方式的变化
  • 基类和派生类对象的赋值转换
  • 继承中的作用域
  • 派生类的默认成员函数
  • 继承和友元
  • 继承与静态成员
  • 复杂的菱形继承及菱形的虚拟继承
    • 菱形继承的概念
    • 虚拟继承
    • **==虚拟继承的原理==**:
  • 继承的总结和反思
    • 继承和组合

继承的概念及定义

继承的概念

继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称为派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。

#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;
};
//继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分,这里体现了Student和Teacher复用了Person的成员。如果我们使用监视窗口查看Student和Teacher对象,可以看到变量的复用,调用Print可以看到成员函数的复用。
class Student :public Person//以public的方式继承Person类
{
protected:
	int _Stuid;
};
class Teacher :public Person
{
protected:
	int _Teaid;
};
int main()
{
	Student s;
	Teacher t;
	s.print();
	t.print();

	return 0;
}

继承的定义

定义格式

下面我们看到Person是父类,也称作基类。Student是子类,也称作派生类
在这里插入图片描述

继承关系和访问限定符

在这里插入图片描述

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

在这里插入图片描述
总结:

  1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是在语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
  2. 基类private成员在派生类中是不能被访问的,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
  3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见的基类的其他成员在子类的访问形式==min(成员在基类的访问限定符,继承方式),public>protected>private。
  4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式
  5. 在实际应用中一般使用都是public继承,几乎很少使用protected/private继承,也不提倡使用protected/private继承,因为protected/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。

实例演示三种继承关系下基类成员的各类型成员访问关系变化

#include<iostream>
#include<string>
using namespace std;
class Person
{
public:
	void print()
	{
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
	}
protected:
	string _name="peter";
private:
	int _age=18;
};

//class Student : protected Person
//class Student : private Person
//基类一般不采用protected和private的方式继承,因为这样基类的函数只能在派生类中使用,
//无法在类外使用
class Student :public Person//以public的方式继承Person类
{
	void test()
	{
		cout << _name << endl;
		//cout << _age << endl;//报错error,因为基类的私有成员在类内和类外都不可见
	}
protected:
	int _Stuid;
};

class Teacher :public Person
{
protected:
	int _Teaid;
};
int main()
{
	Student s;
	Teacher t;
	s.print();
	t.print();

	return 0;
}

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

  • 派生类对象可以赋值给基类的对象/基类的指针/基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。
  • 基类对象不能赋值给派生类对象。
  • 基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(Run-Time Type Information)的dynamic_cast 来进行识别后进行安全转换。
    在这里插入图片描述
#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;
}

继承中的作用域

  1. 在继承体系中基类派生类都有独立的作用域
  2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。这个时候编译器是以子类为优先。但是如果我们就是想要访问父类的该成员变量,就需要加上修饰(在子类成员函数中,可以使用基类::基类成员 显示访问)
  3. 需要注意的是如果成员函数隐藏,只需要函数名相同就构成隐藏
  4. 注意在实际中在继承体系里面最好不要定义同名的成员
#include<iostream>
#include<string>
using namespace std;
class Person
{
protected:
	string _name = "小李子"; // 姓名
	int _num = 111; 	   // 身份证号
};

class Student : public Person
{
public:
	void Print()
	{
		cout << " 姓名:" << _name << endl;
		cout << " 学号:" << _num<< endl;
		cout << " 身份证号:" << Person::_num << endl;
	}
protected:
	int _num = 999; // 学号,派生类优先
};

如何区分函数重载和继承中的隐藏(重定义)以及函数覆盖?

我们首先来回顾一下构成函数重载的前提

  1. 同一个作用域下
  2. 函数名必须相同
  3. 函数的参数类型不同 或者 个数不同 或者 顺序不同
  4. 注意函数的返回值不能作为函数重载的条件

函数重载:使得函数名相同,提高函数的复用性

函数覆盖:发生在子类和父类之间,如果子类的函数与父类的函数,名称和参数完全相同且基类函数必须使用virtual关键字修饰,则发生覆盖。借助于虚函数可以实现多态性。

函数隐藏因为派生类函数和基类函数不满足在同一作用域这个条件,所以两者的成员函数并不构成函数重载,如果派生类函数与基类函数同名,但参数不同,此时,无论是否有virtual关键字,基类的所有同名函数都将被隐藏,而不会重载,因为不在同一个类中;

如果派生类函数与基类函数同名,且参数也相同,但基类函数没有用virtual关键字声明,则基类的所有同名函数都将被隐藏,而不会覆盖,因为没有声明为虚函数。

那么,函数隐藏的应用场景是什么:假若没有隐藏机制,改变基类实现将有可能影响到子类已经正常工作的代码出现未预料的行为,这不是我们希望看到的.

举例:以下代码的两个func函数不是构成函数重载,而是构成隐藏(重定义)。

class A
{
public:
	void fun()
	{
		cout << "A::func()" << endl;
	}
};
class B : public A
{
public:
	void fun(int i)
	{
		cout << "B::func(int i)->" << i << endl;
	}
};

void Test()
{
	B b;
	b.fun(10);
};

例如,对于以下代码,调用成员函数fun时将直接调用子类当中的fun,若想调用父类当中的fun,则需使用作用域限定符指定类域。

#include <iostream>
#include <string>
using namespace std;
//父类
class Person
{
public:
	void fun(int x)
	{
		cout << x << endl;
	}
};
//子类
class Student : public Person
{
public:
	void fun(double x)
	{
		cout << x << endl;
	}
};
int main()
{
	Student s;
	s.fun(3.14);       //直接调用子类当中的成员函数fun
	s.Person::fun(20); //指定调用父类当中的成员函数fun
	return 0;
}

派生类的默认成员函数

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

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

在这里插入图片描述

#include<iostream>
#include<string>
using namespace std;
class Person
{
public:
	Person(const char* name)//构造函数
		: _name(name)
	{
		cout << "Person()" << endl;
	}

	Person(const Person& p)//拷贝构造函数
		: _name(p._name)
	{
		cout << "Person(const Person& p)" << endl;
	}

	Person& operator=(const Person& p)//赋值重载函数
	{
		cout << "Person operator=(const Person& p)" << endl;
		if (this != &p)
			_name = p._name;

		return *this;
	}

	~Person()
	{
		cout << "~Person()" << endl;
	}
protected:
	string _name; // 姓名
};

// 派生类中
// 1、构造函数,父类成员调用父类的构造函数完成初始化
class Student : public Person
{
public:
	Student(const char*name, int num)
		:Person(name)
		, _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=(const Student& s)" << endl;
		if (this != &s)
		{
			Person::operator=(s);
			_num = s._num;
		}

		return *this;
	}

protected:
	int _num; //学号
};

int main()
{
	Student s1("张三", 18);
	Student s2(s1);

	Student s3("李四", 20);

	s1 = s3;

	return 0;
}

继承和友元

基类的友元函数不能继承,也就是说基类的友元函数不能访问子类的保护或私有成员

#include<iostream>
using namespace std;
class Person
{
	friend void Show(const Person& p, class Student& s);
protected:
	int _age;
};
void Show(const Person& p,class Student& s)
{
	cout << p._age << endl;
	//cout << s._age << endl;//这里会报错:使用了未定义类型的Student,
	//因为友元函数没有继承到子类,无法访问到子类的保护或私有成员
}
class Student: public Person
{
	friend void Show(const Person& p, class Student& s);
protected:
	int _id;
};
int main()
{
	Person p;
	Student s;

	Show(p, s);

	return 0;
}

继承与静态成员

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例。因为静态成员属于整个类,而不是属于某个对象,同时也属于所有派生类及其对象。

//继承与静态成员
#include<iostream>
using namespace std;
class Person
{
public:
	Person()
	{
		_count++;
	}
	void Print()//这里有隐藏的this指针
	{
		cout << this << endl;
	}
protected:
	string _name;
public:
	static int _count;
};
//静态成员变量属于整个类,而不是单独属于某个对象,它同时也属于派生类及其对象
int Person::_count = 0;//在类外初始化静态变量

class Student: public Person
{
protected:
	int _id;
};
class Graduate :public Student
{
protected:
	int _num;
};
int main()
{
	Person p;
	Student s;


	cout << p._count << endl;//2
	cout << s._count << endl;//2
	cout << &p._count << endl;//地址相同
	cout << &s._count << endl;


	Graduate g;
	cout << g._count << endl;//3
	cout << &g._count << endl;//地址相同

	cout << Person::_count << endl;//3


	//需要注意的特殊情况:空指针
	Person* ptr = nullptr;
	cout << ptr->_count << endl;//ok,因为这里将nullptr传给this指针,并没有对this指针进行解引用访问对象里面的成员
	ptr->Print();//ok,Person类中的Print函数只是打印this的地址,并没有访问对象中的成员
	//cout << ptr->_name << endl;  // no,因为访问到对象里面的成员了

	//与以上一样的原因
	(*ptr).Print();             // ok
	cout << (*ptr)._count << endl; // ok
	return 0;
}

复杂的菱形继承及菱形的虚拟继承

菱形继承的概念

单继承:当一个子类只有一个直接父类时称这个继承关系为单继承
在这里插入图片描述

多继承:当一个子类有两个或两个以上的直接父类时称这个继承关系为多继承
在这里插入图片描述
菱形继承:是多继承的一种特殊情况。
在这里插入图片描述
菱形继承存在的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。在Assistant的对象中Person成员会有两份。
在这里插入图片描述
下列的代码是菱形继承出现的典型问题:代码冗余和二义性。

#include <iostream>
#include <string>
using namespace std;
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 _majorCourse; //主修课程
};
int main()
{
	Assistant a;
	//这里会报错
	a._name = "peter"; //二义性:无法明确知道要访问哪一个_name
	return 0;
}

Assistant对象是多继承的Student和Teacher,而Student和Teacher当中都继承了Person,因此Student和Teacher当中都有_name成员,若是直接访问Assistant对象的_name成员会出现访问不明确的报错。

对于此,我们可以显示指定访问Assistant哪个父类的_name成员

//显示指定访问哪个父类的成员
a.Student::_name = "张同学";
a.Teacher::_name = "张老师";

这种显示指定类域的方法可以解决二义性的问题,但是依然解决不了菱形继承数据冗余的问题。下面引出虚拟继承。

虚拟继承

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

之后就算我们指定访问Assistant的Student父类和Teacher父类的_name成员,访问到的都是同一个结果,同一块地址,这解决了二义性的问题

#include <iostream>
#include <string>
using namespace std;
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 _majorCourse; //主修课程
};
int main()
{
	Assistant a;
	a._name = "peter"; //无二义性
	return 0;
}

虚拟继承的原理

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

虚拟继承的原理结构较复杂,所以我们先看看不使用菱形虚拟继承,观察以下菱形继承当中D类对象的各个成员在内存中的发布情况。
测试代码如下:

#include <iostream>
using namespace std;
class A
{
public:
	int _a;
};
class B : public A
{
public:
	int _b;
};
class C : 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成员。

现在我们再来看看使用菱形虚拟继承时,以下菱形继承当中D类对象的各个成员在内存当中的分布情况。

测试代码:

#include <iostream>
using namespace std;
class A
{
public:
	int _a;
};
class B : virtual public A
{
public:
	int _b;
};
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的两个指针,指向的一张表,。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的是偏移量,B和C通过这个偏移量可以找到下面的A。第二个数据就是当前类对象位置距离公共虚基类的偏移量。
在这里插入图片描述
我们若是将D类对象赋值给B类对象,在这个切片过程中,就需要通过虚基表中的第二个数据找到公共虚基类A的成员,得到切片后该B类对象在内存中仍然保持这种分布情况。

D d;
B b = d; //切片行为

继承的总结和反思

1.很多人说C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱
形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设
计出菱形继承。否则在复杂度及性能上都有问题。

  • 多继承可以认为是C++的缺陷之一,很多后来的OO语言都没有多继承,如Java。

继承和组合

  • public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。
    如男人(Man)是人(Human)的一种,女人(Woman)是人的一种。那么类Man 可以从类Human 派生,类Woman也可以从类Human 派生。示例程序如下:

class Human 
{
  …
};
class Man : public Human 
{
  …
};
class Woman : public Human 
{
  …
};

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

例如眼(Eye)、鼻(Nose)、口(Mouth)、耳(Ear)是头(Head)的一部分,所以类Head 应该由类Eye、Nose、Mouth、Ear 组合而成,不是派生而成。示例程序如下:

class Eye
{
public:
  void Look(void);
};

class Nose
{
public:
  void Smell(void);
};

class Mouth
{
public:
  void Eat(void);
};

class Ear
{
public:
  void Listen(void);
};

// 正确的设计,冗长的程序
class Head
{
public:
  void Look(void) { m_eye.Look(); }
  void Smell(void) { m_nose.Smell(); }
  void Eat(void) { m_mouth.Eat(); }
  void Listen(void) { m_ear.Listen(); }
private:
  Eye m_eye;
  Nose m_nose;
  Mouth m_mouth;
   Ear m_ear;
};


若是两个类之间既可以看作is-a的关系,又可以看作has-a的关系,则优先使用组合

原因:

  1. 继承允许根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为“白箱”复用。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见。所以继承在一定程度上破坏了基类的封装。并且基类的改变,对派生类有很大的影响,即派生类和基类间的依赖关系很强,耦合度高。
  2. 对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为“黑箱”复用,因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于保持每个类被封装。
  3. 实际中尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系适合用继承就用继承,另外要实现多态,也必须要继承,类之间的关系可以用继承,可以用组合,就用组合。

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

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

相关文章

IT6512可编程直流电源的工作原理

现在各种的电子设备不断的发展&#xff0c;它们对直流供电的电源也有了更高的要求&#xff0c;相对于电子设备来说&#xff0c;用单一的直流电源是没有办法达到供电的要求&#xff0c;所以需要不同的直流电源来给电子设备供电。可编程直流电源就是这一种。在生产测试中&#xf…

Pandas的apply, map, transform介绍和性能测试

apply函数是我们经常用到的一个Pandas操作。虽然这在较小的数据集上不是问题&#xff0c;但在处理大量数据时&#xff0c;由此引起的性能问题会变得更加明显。虽然apply的灵活性使其成为一个简单的选择&#xff0c;但本文介绍了其他Pandas函数作为潜在的替代方案。 在这篇文章…

软测(基础)· 软件测试的生命周期 · 如何描述一个 Bug · Bug 的级别 · Bug 的生命周期 · 争执 · Bug 评审

一、软件测试的生命周期软件测试的生命周期 & 软件开发的生命周期二、如何描述一个 Bug三、如何定义 Bug 的级别四、Bug 的生命周期五、发生争执了怎么办&#xff1f;Bug 评审一、软件测试的生命周期 软件测试的生命周期&#xff1a;需求分析 → 测试计划 → 测试设计、测…

《巫师3:狂猎》4.01版更新 PC端已上线

去年12月&#xff0c;《巫师3》免费升级次世代版&#xff0c;加入DLSS 3支持&#xff0c;RTX 40系显卡的用户能直接提升体验感&#xff0c;RTX 30系用户能通过DLSS 2获得更稳定的帧数。 目前。《巫师3&#xff1a;狂猎》4.01版已更新上线&#xff0c;在PC、PlayStation 和 Xbo…

【配电网规划】配电网N-1扩展规划研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

OJ万题详解––[NOIP2010 提高组] 机器翻译(C++详解)

题目背景 小晨的电脑上安装了一个机器翻译软件&#xff0c;他经常用这个软件来翻译英语文章。 题目描述 这个翻译软件的原理很简单&#xff0c;它只是从头到尾&#xff0c;依次将每个英文单词用对应的中文含义来替换。对于每个英文单词&#xff0c;软件会先在内存中查找这个单词…

openstack cinder对接两个ceph后端配置

需求 需要做卷迁移的工作&#xff0c;从一个ceph集群迁移到另一个集群&#xff0c;因此需要配置两个ceph后端。由此开展后续工作&#xff0c;将配置过程及出现的问题做一记录。 另外两套ceph后端的访问用户都是cinder用户&#xff0c;网上找的资料均为两个用户&#xff0c;当为…

电子技术——BJT的物理结构

电子技术——BJT的物理结构 本节我们介绍另一种基本三端元件&#xff0c;BJT。 物理结构 下图展示了NPN型和PNP型BJT的物理结构简图。 从图中看出&#xff0c;BJT主要由三个区域组成&#xff0c;发射极&#xff08;n类型&#xff09;&#xff0c;基极&#xff08;p类型&#…

如何跑起一个Python Flask 项目

最近做项目迁移&#xff0c;从Google cloud 迁移到 AWS项目&#xff1a;Python Flask ORM是Alembic(我不是搞python的 这边看到这个了)python 是docker 跑起来的&#xff0c;一个docker-compose up就完事但我要进行数据库迁移测试&#xff0c;所以本地要跑起来我是mac先安装pyt…

财报解读:大裁员后Meta的元宇宙还有新故事吗?

美股科技巨头Facebook自更名为Meta Platforms后全面发力元宇宙&#xff0c;作为美国第一大社交平台以及全球流量池&#xff0c;转型后的Meta一度被市场寄予厚望&#xff0c;但同样受累于其元宇宙策略&#xff0c;年初至今&#xff0c;Meta的股价累计一度下跌近65%&#xff0c;也…

【超详细】一文看懂如何在PyCharm中集成Git

PyCharm环境集成Git 当我们在官网下载好Git后&#xff0c;按照要求进行安装&#xff0c;就可以通过快捷方式对本地仓库进行版本控制啦。但是这种方式处理整个工作环境还是比较麻烦的&#xff0c;接下来&#xff0c;我们将在PyCharm环境中配置Git。 基础配置 在设置中&#xf…

IPV6基本了解

参考&#xff1a;https://support.huawei.com/enterprise/zh/doc/EDOC1100116138#ZH-CN_TOPIC_0204809629&#xff0c; https://www.w3cschool.cn/ipv6/ipv6_address_types.html IPv6地址结构 和IPv4的10进制的表示方式不同&#xff0c;IPv6使用的是16进制的表示方式。 首先基…

FreeRTOS内存管理

内存管理是一个系统基本组成部分&#xff0c;FreeRTOS 中大量使用到了内存管理&#xff0c;比 如创建任务、信号量、队列等会自动从堆中申请内存。用户应用层代码也可以使 用 FreeRTOS 提供的内存管理函数来申请和释放内存。本章要实现的功能是&#xff1a;使 用 heap_4.c 方案…

剑指Offer 第21天 不用加减乘除做加法 二进制中1的个数

剑指 Offer 65. 不用加减乘除做加法 写一个函数&#xff0c;求两个整数之和&#xff0c;要求在函数体内不得使用 “”、“-”、“*”、“/” 四则运算符号。 int add(int a, int b) {while(b ! 0){unsigned int c (unsigned)(a & b)<<1;a a ^ b;b c;}return a;} 剑…

Linux安装Mysql5.5

链接&#xff1a;https://pan.baidu.com/s/146KA6VfB4NW6mWSRRwXsMg 提取码&#xff1a;ib17 rpm安装Mysql5.5 检测Mysql是否安装 强制卸载原来的Mysql 安装Mysql服务端 安装Mysql客户端 启动Mysql------> service mysql start 连接Mysql------->mysql -u ro…

时序数据库

时序数据库(TSDB) 接下来就到了&#xff0c;自己所适应行业的数据库了&#xff0c;时许数据库&#xff0c;这类对物联网传感器数据有着很好的支持。 https://blog.csdn.net/firewater23/article/details/125697248 时序数据是随时间不断产生的一系列数据&#xff0c;简单来说…

AD936x_增益控制AGC详解

增益控制概述 所有AGC模式都可用于TDD和FDD场景。AD936x具有手动增益控制选项&#xff0c;允许基带处理器控制接收机的增益。 上图为AD936x接收信号路径示意图&#xff0c;每个接收机都有自己的增益表&#xff0c;将增益控制字映射到每个可变增益块。无论使用AGC还是手动增益控…

ABAP IDOC 测试及使用相关事务代码

WE02:查看IDOC日志和清单 WE19:测试IDOC 可以进入debug模式 WE20:维护伙伴的一些属性&#xff0c;比如如果加了增强结构&#xff0c;在这里可以增加 WE30:查看并且修改IDOC types 结构 WE31:查看SEGMENT 内的字段和版本。也可以新建segment WE82: 新增输出类型和assignment…

中间件Canal之Canal简单使用

一. 简单介绍 Canal是Java开发的基于数据库增量日志解析&#xff0c;提供增量数据订阅&消费的中间件。目前&#xff0c;Canal主要支持了MySQL的Binlog解析&#xff0c;解析完成后才能利用Canal Client来处理获得的相关数据。 二. MySQL的Binlog 2.1. Binlog是什么&#…

代码随想录算法训练营第37天 回溯算法 java :134. 加油站 135. 分发糖果 1005.K次取反后最大化的数组和

文章目录LeetCode 134. 加油站思路AC代码LeetCode135. 分发糖果思路AC代码LeetCode 1005.K次取反后最大化的数组和思路AC代码总结LeetCode 134. 加油站 思路 两个数组一个是 增加汽油量 gas[ ] 一个耗费汽油量 cost[ ] 可以换一个思路&#xff0c;首先如果总油量减去总消耗大…