C++学习第十三天(多态)

news2024/11/17 8:36:16

1、多态的概念

概念

就是指多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生不同的状态

2、多态的定义及实现

多态的构成条件

多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。

要在继承中构成多态还有两个条件:

  1. 必须通过基类的指针或者引用调用虚函数
  2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

 虚函数

虚函数:即被virtual修饰的类成员函数

class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "apple" << endl;
	}
};

虚函数的重写

虚函数的重写:派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。

class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "apple" << endl;
	}
};

class Student : public Person
{
public:
	virtual void BuyTicket()
	{
		cout << "banana" << endl;
	}
};

void Func(Person& p)
{
	p.BuyTicket();
}

int main()
{
	Person ps;
	Student st;
	Func(ps);
	Func(st);
	return 0;
}

需要注意的是:在重写基类虚函数时,派生类的虚函数不加virtual关键字时,虽然也可以构成重写,但是不建议这样写(因为继承后基类的虚函数被继承下来类在派生类依旧保持虚函数属性),但是这种写法不是规范的写法,不建议这样子使用


虚函数重写的两个例外:

  1. 协变(基类与派生类虚函数返回值类型不同)
    派生类重写虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变
    class A{};
    class B : public A {};
    
    class Person
    {
    public:
    	virtual A* f() { return new A; }
    };
    
    class Student : public Person
    {
    public:
    	virtual B* f() { return new B; }
    };
  2. 析构函数的重写(基类与派生类析构函数的名字不同)
    如果基类的析构函数为虚函数此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后的析构函数的名称统一处理为destructor
     

    class Person
    {
    public:
    	virtual ~Person() { cout << "~Person()" << endl; }
    };
    
    class Student : public Person
    {
    public:
    	virtual ~Student() { cout << "~Student()" << endl; }
    };
    
    int main()
    {
    	Person* p1 = new Person;
    	Person* p2 = new Student;
    	delete p1;
    	delete p2;
    	return 0;
    }

C++11 override 和 final

C++提供了override和final来帮助用户检测是否重写

  1. final:修饰虚函数,表示该虚函数不能再被重写
    class Car
    {
    public:
    	virtual void Drive() final{}
    };
    
    class Benz : public Car
    {
    public:
    	virtual void Drive() { cout << "Benz" << endl; }
    };

  2. override:检查派生类虚函数是否重写了基类某个基函数,如果没有重写就错
    class Car
    {
    public:
    	virtual void Drive() {}
    };
    
    class Benz : public Car
    {
    public:
    	virtual void Drive() override { cout << "Benz" << endl; }
    };

 重载、覆盖(重写)、隐藏(重定义)的对比

三个概念的对比重载两个函数在同一作用域
函数名/参数相同
重写(覆盖)两个函数分别在基类和派生类的作用域
函数名/参数/返回值都必须相同(协变除外)
两个函数必须是虚函数
重定义(隐藏)两个函数分别在基类和派生类的作用域
函数名相同
两个基类和派生类的同名函数不构成重写就是重定义

 3、抽象类

 概念

在虚函数的后面写上 = 0,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫做抽象类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象纯虚函数规范类派生类必须重写,另外纯虚函数更体现出了接口继承

class Car
{
public:
	virtual void Drive() = 0;
};

class Benz : public Car
{
public:
	virtual void Drive()
	{
		cout << "Benz" << endl;
	}
};

class BMW : public Car
{
public:
	virtual void Drive()
	{
		cout << "BMW" << endl;
	}
};

void Test()
{
	Car* pBenz = new Benz;
	pBenz->Drive();

	Car* pBMW = new BMW;
	pBMW->Drive();
}

int main()
{
	Test();
	return 0;
}

 接口继承和实现继承

普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,就不要把函数定义为虚函数

 4、多态的原理

虚函数表

class Base
{
public:
	virtual void Func1()
	{
		cout << "Func1()" << endl;
	}

private:
	int _b = 1;
};

在x86环境下运行出来是sizeof(Base)是8个字节,除了_b成员,还多了一个_vfptr放在对象的最后面,和这个平台有关,对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)。一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要放在虚函数表中,虚函数表也简称虚表,那么派生类中的这个表中放

了些什么?

针对上面的代码我们可以做出以下改造:

  1. 我们增加一个派生类Derive继承Base
  2. Derive中重写Func1
  3. Base再增加一个虚函数Func2和一个普通函数Func3
class Base
{
public:
	virtual void Func1() 
	{
		cout << "Base::Func1()" << endl;
	}

	virtual void Func2()
	{
		cout << "Base::Func2()" << endl;
	}

	virtual void Func3()
	{
		cout << "Base::Func3()" << endl;
	}
private:
	int _b = 1;

};

class Derive : public Base
{
public:
	virtual void func1()
	{
		cout << "Derive::func1()" << endl;
	}

private:
	int _d = 2;
};

int main()
{
	Base b;
	Derive d;
	return 0;
}

我们可以发现一下问题:

  1. 派生类对象d中也有一个虚表指针,d对象由两部分构成,一部分是父类继承下来的成员,虚表指针也就是存在部分的另一部分是自己的成员
  2. 基类b对象个派生类d对象虚表式不一样的,这里我们发现Func1完成了重写,所以d的虚表中存的是重写的Derive::Func1,所以虚函数重写也叫做覆盖,覆盖就是指虚表中虚函数的覆盖,重写的是语法的叫法,覆盖是原理层的叫法
  3. 另外Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函数,所以不会放进虚表
  4. 虚函数表本质是一个存虚函数指针的指针数组,一般情况下这个数组最后放了一个nullptr。
  5. 总结派生类的虚表构成:先将基类中的虚表内容拷贝一份到派生类虚表中;如果派生类重写了基类中的某个虚函数,用派生类自己的虚函数覆盖虚表中的基类的虚函数;派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。
  6. 这里还有一个问题:虚函数存放在哪里?虚表存在哪里?一般会说:虚函数存放在虚表,虚表存在对象中。但这个是错误的回答。正确答案是:虚表存的是虚函数指针,而不是虚函数,虚函数和普通函数是一样的,都是存在代码段的,只是他的指针又存到了虚表中。另外对象中存放的不是虚表,存的是虚表指针。那么虚表存在哪里的呢?实际上在vs环境下是存在代码段的

 多态的原理

我们在本篇的最开始写了一段代码,那里的Func函数传Person调用的Person::BuyTicket(),传Student调用的是Student::BuyTicket()

  1. 观察此图的红色箭头我们可以知道,p指向mike对象时,p->BuyTicket在mike的虚表中找到虚函数是Person::BuyTicket
  2. 观察此图的蓝色箭头我们可以知道,p指向johnson对象时,p->BuyTicket在johnson的虚表中找到虚函数是Student::BuyTicket
  3. 这样就实现了不同对象去完成同一行为时,展现出不同的形态
  4. 反过来要达到多态,要达到两个条件,一个是虚函数覆盖,一个是对象的指针或引用调用虚函数,为什么?
  5. 通过以下的汇编代码分析,可以看出多态以后的函数调用,不是在编译时确定的,是运行起来以后到对象的中去找的。不满足多态的函数调用时编译器确认好的
void Func(Person* p)
{
    p->BuyTicket();
}
int main()
{
    Person mike;
    Func(&mike);
    mike.BuyTicket();
    
    return 0;
}
// 以下汇编代码中跟你这个问题不相关的都被去掉了
void Func(Person* p)
{
...
    p->BuyTicket();
    // p中存的是mike对象的指针,将p移动到eax中
    001940DE  mov         eax,dword ptr [p]
    // [eax]就是取eax值指向的内容,这里相当于把mike对象头4个字节(虚表指针)移动到了edx
    001940E1  mov         edx,dword ptr [eax]
    // [edx]就是取edx值指向的内容,这里相当于把虚表中的头4字节存的虚函数指针移动到了eax
    00B823EE  mov         eax,dword ptr [edx]
    // call eax中存虚函数的指针。这里可以看出满足多态的调用,不是在编译时确定的,是运行起来
    以后到对象的中取找的。
    001940EA  call        eax  
    00头1940EC  cmp         esi,esp  
}
int main()
{
... 
// 首先BuyTicket虽然是虚函数,但是mike是对象,不满足多态的条件,所以这里是普通函数的调
用转换成地址时,是在编译时已经从符号表确认了函数的地址,直接call 地址
    mike.BuyTicket();
    00195182  lea         ecx,[mike]
    00195185  call        Person::BuyTicket (01914F6h)  
... 
}

 动态绑定和静态绑定

  1. 静态绑定又称为前期绑定,在程序编译期间确定了程序的行为,也称为静态多态,比如函数重载
  2. 动态绑定又称为后期绑定,实在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态

5、单继承和多继承关系的虚函数表

单继承中的虚函数表

class Base
{
public:
	virtual void func1() { cout << "Base::func1()" << endl; }
	virtual void func2() { cout << "Base::func2()" << endl; }
private:
	int a;
};

class Derive : public Base
{
public:
	virtual void func1() { cout << "Derive::func1()" << endl; }
	virtual void func3() { cout << "Derive::func3()" << endl; }
	virtual void func4() { cout << "Derive::func4()" << endl; }
private:
	int b;
};

在监视窗口中,我们发现看不见func3和func4。这里是编译器的监视窗口故意隐藏了这两个函数,相当于是编译器的一个bug。如何查看d中的虚表呢,我们可以用代码查看虚表中的函数

typedef void(*VFPTR) ();
void PrintVTable(VFPTR vTable[])
{
	// 依次取虚表中的虚函数指针打印并调用。调用就可以看出存的是哪个函数
	cout << " 虚表地址>" << vTable << endl;
	for (int i = 0; vTable[i] != nullptr; ++i)
	{
		printf(" 第%d个虚函数地址 :0X%x,->", i, vTable[i]);
		VFPTR f = vTable[i];
		f();
	}
	cout << endl;
}
int main()
{
	Base b;
	Derive d;
	VFPTR* vTableb = (VFPTR*)(*(int*)&b);
	PrintVTable(vTableb);
	VFPTR* vTabled = (VFPTR*)(*(int*)&d);
	PrintVTable(vTabled);
	return 0;
}

代码思路:取出b、d对象的头四个字节,就是虚表的指针,前面我们说了虚函数表本质是一个存虚函数指针的指针数组,这个数组最后面放了一个nullptr

  1. 先取出b的地址,强转成int*的指针
  2. 再解引用取值,就取到了b对象头4字节的值,这个值就是指向虚表的指针
  3. 再强壮成VFPTR*,因为虚表就是一个存VFPTR类型(虚函数指针类型)的数组
  4. 虚表指针传递给PrintVTable进行打印虚表
  5. 需要说明的是这个打印虚表的代码经常会崩溃,因为编译器对虚表的处理不干净,虚表最后面没有放nullptr,导致越界,这是编译器的问题。我们只需要清理解决方案就可以了

 

多继承中的虚函数表 

class Base1
{
public:
	virtual void func1() { cout << "Base1::func1" << endl; }
	virtual void func2() { cout << "Base1::func2" << endl; }
private:
	int b1;
};

class Base2
{
public:
	virtual void func1() { cout << "Base2::func1" << endl; }
	virtual void func2() { cout << "Base2::func2" << endl; }
private:
	int b2;
};

class Derive : public Base1, public Base2
{
public:
	virtual void func1() { cout << "Derive::func1" << endl; }
	virtual void func3() { cout << "Derive::func2" << endl; }
private:
	int d1;
};

typedef void(*VFPTR)();
void PrintVTable(VFPTR VTable[])
{
	cout << " 虚表地址>" << VTable << endl;
	for (int i = 0; VTable[i] != nullptr; ++i)
	{
		printf(" 第%d个虚函数地址:0x%x,->", i, VTable[i]);
		VFPTR f = VTable[i];
		f();
	}
	cout << endl;
}

int main()
{
	Derive d;

	VFPTR* VTableb1 = (VFPTR*)(*(int*)&d);
	PrintVTable(VTableb1);

	VFPTR* VTableb2 = (VFPTR*)(*(int*)((char*)&d+sizeof(Base1)));
	PrintVTable(VTableb2);
	return 0;
}

我们可以看出来:多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中

一些问题

1. 下面哪种面向对象的方法可以让你变得富有( a ) A: 继承 B: 封装 C: 多态 D: 抽象

2. ( d )是面向对象程序设计语言中的一种机制。这种机制实现了方法的定义与具体的对象无关,而对方法的调用则可以关联于具体的对象。

A: 继承 B: 模板 C: 对象的自身引用 D: 动态绑定

3. 面向对象设计中的继承和组合,下面说法错误的是?(c)

A:继承允许我们覆盖重写父类的实现细节,父类的实现对于子类是可见的,是一种静态复用,也称为白盒复用

B:组合的对象不需要关心各自的实现细节,之间的关系是在运行时候才确定的,是一种动态复用,也称为黑盒复用

C:优先使用继承,而不是组合,是面向对象设计的第二原则

D:继承可以使子类能自动继承父类的接口,但在设计模式中认为这是一种破坏了父类的封装性的表现

4. 以下关于纯虚函数的说法,正确的是(a )

A:声明纯虚函数的类不能实例化对象 B:声明纯虚函数的类是虚基类

C:子类必须实现基类的纯虚函数 D:纯虚函数必须是空函数

5. 关于虚函数的描述正确的是(b )

A:派生类的虚函数与基类的虚函数具有不同的参数个数和类型 B:内联函数不能是虚函数

C:派生类必须重新定义基类的虚函数 D:虚函数可以是一个static型的函数

6. 关于虚表说法正确的是( d)

A:一个类只能有一张虚表

B:基类中有虚函数,如果子类中没有重写基类的虚函数,此时子类与基类共用同一张虚表

C:虚表是在运行期间动态生成的

D:一个类的不同对象共享该类的虚表

7. 假设A类中有虚函数,B继承自A,B重写A中的虚函数,也没有定义任何虚函数,则(d )

A:A类对象的前4个字节存储虚表地址,B类对象前4个字节不是虚表地址

B:A类对象和B类对象前4个字节存储的都是虚基表的地址

C:A类对象和B类对象前4个字节存储的虚表地址相同

D:A类和B类虚表中虚函数个数相同,但A类和B类使用的不是同一张虚表

8. 下面程序输出结果是什么? (a)

菱形虚拟继承之中,A的代码只有一份

using namespace std;
class A {
public:
	A(char* s) { cout << s << endl; }
	~A() {}
};
class B :virtual public A
{
public:
	B(char* s1, char* s2) :A(s1) { cout << s2 << endl; }
};
class C :virtual public A
{
public:
	C(char* s1, char* s2) :A(s1) { cout << s2 << endl; }
};
class D :public B, public C
{
public:
	D(char* s1, char* s2, char* s3, char* s4) :B(s1, s2), C(s1, s3), A(s1)
	{
		cout << s4 << endl;
	}
};
int main() {
	D* p = new D("class A", "class B", "class C", "class D");
	delete p;
	return 0;
}

A:class A class B class C class D B:class D class B class C class A

C:class D class C class B class A D:class A class C class B class D

9. 多继承中指针偏移问题?下面说法正确的是(c )

class Base1 {  public:  int _b1; };
class Base2 {  public:  int _b2; };
class Derive : public Base1, public Base2 { public: int _d; };
int main(){
 Derive d;
 Base1* p1 = &d;
 Base2* p2 = &d;
 Derive* p3 = &d;
 return 0;
}

A:p1 == p2 == p3 B:p1 < p2 < p3 C:p1 == p3 != p2 D:p1 != p2 != p3

10. 以下程序输出结果是什么(b)

using namespace std;
class A
{
public:
	virtual void func(int val = 1) { std::cout << "A->" << val << std::endl; }
	virtual void test() { func(); }
};

class B : public A
{
public:
	void func(int val = 0) { std::cout << "B->" << val << std::endl; }
};

int main(int argc, char* argv[])
{
	B* p = new B;
	p->test();
	return 0;
}

A: A->0 B: B->1 C: A->1 D: B->0 E: 编译出错 F: 以上都不正确

  1. inline函数可以是虚函数吗?答:可以,不过编译器就忽略inline属性,这个函数就不再是 inline,因为虚函数要放到虚表中去。
  2. 静态成员可以是虚函数吗?答:不能,因为静态成员函数没有this指针,使用类型::成员函数 的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表。
  3. 构造函数可以是虚函数吗?答:不能,因为对象中的虚函数表指针是在构造函数初始化列表 阶段才初始化的。
  4. 析构函数可以是虚函数吗?什么场景下析构函数是虚函数?答:可以,并且最好把基类的析构函数定义成虚函数。参考本节课件内容
  5. 对象访问普通函数快还是虚函数更快?答:首先如果是普通对象,是一样快的。如果是指针对象或者是引用对象,则调用的普通函数快,因为构成多态,运行时调用虚函数需要到虚函数表中去查找。
  6. 虚函数表是在什么阶段生成的,存在哪的?答:虚函数表是在编译阶段就生成的,一般情况下存在代码段(常量区)的。
  7. C++菱形继承的问题?虚继承的原理?答:注意这里不要把虚函数表和虚基表搞混了。
  8. 什么是抽象类?抽象类的作用?答:参考(3.抽象类)。抽象类强制重写了虚函数,另外抽象类体现出了接口继承关系。

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

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

相关文章

linux权限维持-HackerPermKeeper

&#x1f512; HackerPermKeeper 通过渗透拿到权限之后&#xff0c;为了不让权限丢失&#xff0c;都会进行权限维持&#xff0c;而在进行权限维持的时候&#xff0c;红队需要花费大量的时候&#xff0c;来验证是否合适&#xff0c;因此在这款工具就诞生 HackerPermKeeper[黑客…

【动态规划】子数组、子串系列I|最大子数组和|环形子数组的最大和|乘积最大子数组|乘积为正数的最长子数组长度

一、最大子数组和 最大子数组和 算法原理&#xff1a; &#x1f4a1;细节&#xff1a; 1.返回值为dp表每个位置的最大值&#xff0c;而不是只看最后一个位置&#xff0c;因为可能最后一个位置都不选 2.可以直接在填dp表的时候就进行返回值的比较 3.如果初始化选择多开一个位…

vue+lodop实现web端打印标签功能

背景&#xff1a;项目要求在web端连接标签打印机&#xff0c;打印收件人信息 lodop打印插件地址&#xff1a;Lodop和C-Lodop官网主站 在项目中使用 1、去官网下载lodop包下载中心 - Lodop和C-Lodop官网主站 windows系统直接下载windows32版的就可以 2、解压安装 点击CLodop…

gtest的编译与使用

文章目录 gtest的编译与使用概述笔记CMake参数官方文档测试程序测试效果END gtest的编译与使用 概述 gTest是 googletest的缩写&#xff0c;如果直接找gTest项目&#xff0c;是找不到的。 库地址 https://github.com/google/googletest.git 迁出到本地后&#xff0c;切到最新…

vue中使用element的i18n语言转换(保姆式教程-保证能用)

1、项目中需要使用的插件&#xff0c;vue2或vue3、element、vue-i18n、js-cookie、vuex我是在vue2中使用 npm i element-ui -S npm i js-cookie -S npm i vue-i18n8.28.2 //因为我项目使用的vue2&#xff0c;直接安装报错了,就下载了固定的版本2、在main.js中引入i18n impor…

买手机参考

买手机一定要看cpu排行榜&#xff0c;避免受外界宣传或干扰等因素等的影响&#xff0c;导致买家消费者被割韭菜。是高等手机还是低等手机&#xff0c;cpu排行榜就是照妖镜。cpu综合处理能力&#xff08;兼顾功耗&#xff09;排行榜如图。 第二张 第三张 第四张

C#语言核心

一、面向对象基本概念 万物皆对象&#xff0c;用程序来抽象&#xff08;形容&#xff09;对象&#xff0c;用面向对象的思想来编程 用中文去形容一类对象&#xff0c;把一类对象的共同点提取出来&#xff0c;然后用程序语言把它翻译过来&#xff0c;带着对象的概念在程序中使…

Excel 查找值的位置后再用位置取值Excel处理

例题描述 Excel 文件中有下图所示的 3 个片区数据 (不同颜色标明)。 现在要算出1-12对应的一列数据&#xff0c;计算规则&#xff1a;在片区3中依次查找1-12&#xff0c;找到后在片区1对应位置取数&#xff0c;如果是0则取片区2同位置的数&#xff0c;如果是1则取F当前查找数。…

docker-compose管理jenkins

1.安装docker和compose 1.docker 更新系统&#xff1a;yum update 安装依赖项&#xff1a;yum install -y yum-utils device-mapper-persistent-data lvm2 配置镜像源&#xff1a;yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce…

出货300万片后,智舱界「小高通」浮出水面

‍作者 |张祥威 编辑 |德新 2024年北京车展&#xff0c;本土芯片公司开始截击外企供应商。 很长一段时间内&#xff0c;汽车行业智驾芯片看英伟达&#xff0c;座舱芯片看高通。英伟达Orin系列广受欢迎&#xff0c;高通8155席卷主流智能汽车&#xff0c;8295更是被视为最强配置…

Seata之AT 模式的使用

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 Seata 是一款开源的…

STM32F4xx开发学习_USART串口通讯

USART串口通讯 USART简介 USART&#xff08;universal synchronous asynchronous receiver transmitter&#xff09;&#xff0c;通用同步异步接收发射机&#xff0c;是一种全双工异步通信串行通讯方式&#xff0c;是STM32内部集成的硬件外设&#xff0c;以帧格式传输数据。搭…

NAT网络基本原理和认识,内网穿透的必备知识

NAT的基本介绍 NAT&#xff08;Network Address Translation&#xff09;是网络地址转换&#xff0c;它是一个IETF(Internet Engineering Task Force, Internet工程任务组)标准&#xff0c;允许一个整体机构以一个公用IP&#xff08;Internet Protocol&#xff09;地址出现在I…

二总线,替代传统485总线通讯,主站设计

二总线通信设计专栏 《二总线&#xff0c;替代传统485总线通讯&#xff0c;选型及应用-CSDN博客》《二总线&#xff0c;替代传统485总线通讯&#xff0c;低成本直流载波方案实现及原理-CSDN博客》《二总线&#xff0c;替代传统485总线通讯&#xff0c;调试避坑指南之最大的电流…

探索鸿蒙开发:鸿蒙系统如何引领嵌入式技术革新

嵌入式技术已经成为现代社会不可或缺的一部分。而在这个领域&#xff0c;华为凭借其自主研发的鸿蒙操作系统&#xff0c;正悄然引领着一场技术革新的浪潮。本文将探讨鸿蒙开发的特点、优势以及其对嵌入式技术发展的深远影响。 鸿蒙操作系统的特点 鸿蒙&#xff0c;作为华为推…

【Java】从0实现一个基于SpringBoot的个人博客系统

从0实现一个基于SpringBoot的个人博客系统 项目介绍准备工作数据准备创建项目准备前端页面编写配置文件 项目公共模块实体类公共层业务代码持久层实现博客列表实现博客列表约定前后端交互接口 实现博客详情约定前后端交互接口实现服务器代码 实现登录JWT令牌JWT令牌生成和校验实…

详解如何把文件或应用开机自启动

相信很多人都想把某些开机都要用的软件或文件打开&#xff0c;但不知道如何操作&#xff0c;或不知道可以手动设置开机自启动这一操作&#xff0c;下面为大家讲解实现思路&#xff1a; 1.首先打开winR打开运行框&#xff0c;在里面弄输入&#xff1a;shell:startup 2.回车进入文…

Win10/11共享文件夹,访问提示需要输入用户名密码

Win10/11共享文件夹&#xff0c;访问提示需要输入用户名密码 问题 已经关闭了密码保护共享&#xff0c;但是局域网其他电脑访问该文件夹&#xff0c;提示需要输入用户名和密码 解决方法 操作步骤 1.按WINR键打开运行&#xff0c;输入gpedit.msc打开本地组策略编辑器 2.按如…

Hive SQL-DML-Load加载数据

Hive SQL-DML-Load加载数据 在 Hive 中&#xff0c;可以使用 SQL DML&#xff08;Data Manipulation Language&#xff09;语句中的 LOAD 命令来加载数据到表中。LOAD 命令用于将本地文件系统或 HDFS&#xff08;Hadoop 分布式文件系统&#xff09;中的数据加载到 Hive 表中。 …

CSAPP | Floating Point

CSAPP | Floating Point b i b_i bi​ b i − 1 b_{i-1} bi−1​ … b 2 b_2 b2​ b 1 b_1 b1​ b 0 b_0 b0​ b − 1 b_{-1} b−1​ b − 2 b_{-2} b−2​ b − 3 b_{-3} b−3​ … b − j b_{-j} b−j​ S ∑ k − j i b k 2 k S\sum_{k-j}^{i}b_k\times2^k S∑k…