【C++】——多态详解

目录

1、什么是多态?

2、多态的定义及实现

2.1多态的构成条件

​2.2多态语法细节处理

2.3协变

2.4析构函数的重写

2.5C++11 override 和 final关键字

2.6重载—重写—隐藏的对比分析

3、纯虚函数和抽象类

4、多态的原理分析

4.1多态是如何实现的

4.2虚函数表  


面向对象技术(OOP)的核心思想就是封装,继承和多态。通过之前的学习,我们认识到什么是封装,什么是继承。

封装就是对将一些属性装载到一个类对象中,不受外界的影响,实现一个独立性很强的模块,比如:洗衣机就是对洗衣服功能,甩干功能,漂洗功能等的封装,其功能不会受到外界的微波炉影响。

继承就是可以将类对象进行继承,派生类会继承基类的方法与属性,提高开发效率并降低维护成本,类似父与子的关系。比如蔬菜和黄瓜,黄瓜就有蔬菜的特性。

接下来我们就来学习认识多态!!

1、什么是多态?

多态是面向对象技术(OOP)的核心思想之一,我们称具有继承关系的多个类型称为多态类型,通俗来讲:就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。

多态分为编译时多态(静态多态)运行时多态(动态多态),编译时多态(静态多态)主要就是函数重载和函数模板,他们传不同类型的参数就可以调用不同的函数,通过参数不同达到多种形态,之所以叫编译时多态,是因为他们实参传给形参的参数匹配是在编译时完成的,通常我们把编译时一般归为静态,运行时归为动态。

运行时多态,具体点就是去完成某个行为(函数),可以传不同的对象就会完成不同的行为,就达到多种形态。比如买票这个行为,当普通人买票时,是全价买票;学生买票时,是优惠买票(5折或75折);军人买票时是优先买票。

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

class Person {
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
	virtual void BuyTicket() { cout << "买票-打折" << endl; }
};
void Func(Person* ptr)
{
	// 这里可以看到虽然都是Person指针Ptr在调⽤BuyTicket 
	// 但是跟ptr没关系,而是由ptr指向的对象决定的。 
	ptr->BuyTicket();
}
int main()
{
	Person ps;
	Student st;
	Func(&ps);
	Func(&st);
	return 0;
}

Student继承了Person,Person对象买票全价,而Student对象买票半价

原理是什么呢??

  • 多态调用:运行时,到指定对象的虚表中找虚函数来调用(指向基类调用基类的虚函数,指向子类调用子类的虚函数)
  • 普通调用:编译时,调用对象是哪个类型,就调用它的函数。

一看还挺复杂,接下来我们就来从零学习多态。

2、多态的定义及实现

2.1多态的构成条件

第一大前提!!只有继承情况下才有虚函数,才能实现多态!!!

多态构成的条件:

<1>必须通过基类的指针或者引用调用虚函数(virtual修饰的类成员函数)
<2>被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写(父子虚函数要求三同)

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

补充说明:要实现多态效果,第一必须是基类的指针或引用,因为只有基类的指针或引用才能既指向派生类对象也可以指向基类对象(继承切片);第二派生类必须对基类的虚函数重写/覆盖,重写或者覆盖了,派生类才能有不同的函数,多态的不同形态效果才能达到。

2.2多态语法细节处理

  1. 派生类(基类必须写)的对应函数可以不写virtual(这个语法点非常奇怪!建议写上virtual
  2. “重写”的本质是重新写函数的实现,函数声明(包括缺省参数的值)参考基类的缺省值,与基类保持一致

看一道经典面试题:

#include<iostream>

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: 以上都不正确

答案是:B    为什么呢?

  • 首先
  1. A类与B是继承关系
  2. func函数是虚函数(B类是派生类,可以不写virtual),并且A和B中满足三同构成多态
  3. test定义在基类A中函数的默认参数是基类指针(A* this 成员函数的默认参数),满足多态条件
  • 其次
  1. 主函数中调用test函数,因为B是子类,没有test函数,所以会在父类A中寻找。
  2. test函数调用 func函数满足多态虚函数,基类参数this指向的是B类(指向谁调用谁),所以就会调用B类的func函数B->
  3. 重写的本质是对函数的实现进行重写,函数的结构部分(包括参数,缺省值,函数名,返回值等)与基类一致。所以是 1

2.3协变

多态的条件有着父子虚函数要求三同,但是有一个特殊情况就是协变!

协变:派生类重写基类虚函数时,与基类虚函数返回值类型不同

  1. 基类虚函数返回基类对象的指针或者引用
  2. 派生类虚函数返回派生类对象的指针或者引用

举个例子:

class A {};
class B : public A {};
//返回类型不同但是仍构成多态
class Person {
public:
	//virtual Person* BuyTicket()  自己的类型也是可以只要遵循 基类对应基类
	virtual A* BuyTicket()            //派生类对应派生类即可
	{
		cout << "买票-全价" << endl;
		return nullptr;
	}
};
class Student : public Person {
public:
	//virtual Student* BuyTicket()
	virtual B* BuyTicket()
	{
		cout << "买票-打折" << endl;
		return nullptr;
	}
};
void Func(Person* ptr)
{
	ptr->BuyTicket();
}
int main()
{
	Person ps;
	Student st;
	Func(&ps);
	Func(&st);
		return 0;
}

很明显派生类与基类的返回值不同注意一定是:基类返回“基类”,派生类返回“派生类”):
但是结果确实正常的,依然构成多态,这样的情况就称为协变!!!

2.4析构函数的重写

基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同看起来不符合重写的规则,是因为析构函数在编译阶段都会转换成:destructor(),所以表面析构函数名字不同,但是实质上是一致的。这样就会构成多态。那么为什么有这样的细节处理呢??

正常情况下的析构:

class A
{
public:
	~A()
	{
		cout << "~A()" << endl;
	}
};
class B :public A
{
public:
	~B()
	{
		cout << "~B()" << endl;
	}
};
int main()
{
	A a; 
	B b;
	return 0;
}

这样会正常的调用析构函数(子类析构会自动调用父类析构->先子后父)

那如果是指针或者引用类型呢?

class A
{
public:
	~A()
	{
		cout << "~A()" << endl;
	}
};
class B :public A
{
public:
	~B()
	{
		cout << "~B()" << endl;
	}
};
int main()
{
	//A a;
	//A b;
	//基类指针既可以指向 基类也可以指向派生类 
	A* a = new A;
	//切片指向对应内容
	A* b = new B;
	delete a;
	delete b;
	return 0;
}

竟然只是调用了基类的析构函数??那么派生类的析构函数怎么调用呢?

如果不解决这个问题就会导致内存泄漏

class A
{
public:
	~A()
	{
		cout << "~A()" << endl;
	}
};
class B :public A
{
public:
	~B()
	{
		cout << "~B()->delete" << endl;
		delete _p;
	}
	int* _p = new int[10000000];
};
int main()
{
	for (int i = 0; i < 100000; i++)
	{
		A* p = new B;
		delete p;
	}

	return 0;
}

可以看到派生类B有着资源申请,析构后需要释放空间,但是按照上一个例子调用的是基类的析构函数,那么空间得不到释放就会造成内存泄漏!

这就十分危险了!!!
而我们所希望的是指向谁就调用谁的析构:指向基类调用基类析构,指向派生类调用派生类析构。
那我们怎么做到呢???当然就是多态!!!
那我们来看看现在满不满足多态的条件:

  1. 必须通过基类的指针或者引用调用虚函数(virtual修饰的类成员函数)
  2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写(父子虚函数要求三同)

在编译的时候,析构函数都会变成destructor,这样满足三同!构成重写(甚至可以理解为,编译器处理析构函数为 destructor 就是为了实现多态)

那么只需要最后一步,将析构函数变为虚函数即可!

class A
{
public:
	virtual ~A()
	{
		cout << "~A()" << endl;
	}
};
class B :public A
{
public:
	virtual ~B()//此处可以不加 virtual 最好对应加上
	{
		cout << "~B()->delete" << endl;
		delete _p;
	}
	int* _p = new int[10000000];
};

可以看到实现了我们的目的:指向基类调用基类析构,指向派生类调用派生类析构,应该释放的空间也全部释放了!

所以建议析构函数设置为虚函数,避免出现上述的情况。

2.5C++11 override 和 final关键字

从上面可以看出,C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数名字母次序写反而无法构成重写,而这种错误在编译期间是不会报出的,只有在程序运行时没有得到预期结果才来debug会得不偿失,因此:C++11提供了override和final两个关键字,可以帮助用户检测是否重写。

<1>final        

  • 修饰类(最终类),表示该类不能被继承。(C++98使用private来做到不能继承)
    class car final { };
  • 修饰虚函数,表示该虚函数不能再被继承
    virtual void func() final { }

<2>override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。

// error C3248: “Car::Drive”: 声明为“final”的函数⽆法被“Benz::Drive”重写 
class Car
{
public:
 virtual void Drive() final {}
};

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

2.6重载—重写—隐藏的对比分析

<1>重载

  • 两个函数作用在同一作用域
  • 函数名相同,参数不同,类型或个数不同,返回值无要求

<2>重写

  • 两个函数分别在基类作用域和派生类作用域
  • 函数名、参数、返回值都一样(协变例外)本质:函数体不同
  • 两个函数必须是虚函数

<3>隐藏

  • 两个函数分别在基类作用域和派生类作用域
  • 仅仅函数名相同就可以构成
  • 两个基类和派生类的同名函数不是重写就是重定义

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;
	}
};
int main()
{
	// 编译报错:error C2259: “Car”: ⽆法实例化抽象类 
	Car car;
	Car* pBenz = new Benz;
	pBenz->Drive();
	Car* pBMW = new BMW;
	pBMW->Drive();
	return 0;
}

抽象类与override关键字的区别:

  1. 抽象类间接强制了派生类必须进行虚函数重写
  2. override是在已经重写的情况下,帮助进行重写的语法检查

4、多态的原理分析

4.1多态是如何实现的

多态类和普通类的区别是什么呢?——虚函数!

先来看看具有虚函数的类的大小:

//32位环境
class Base
{
public:
	virtual void Func1()
	{
		cout << "Func1()" << endl;
	}
protected:
	int _b = 1;
	char _ch = 'x';
};

int main()
{
	Base b;
	cout << sizeof(b) << endl;
	return 0;
}

如果是没有虚函数Func1的话,那么就是  4+1 然后对齐一下8个字节。

运行一下:

Base的大小在x86环境下是12字节。这十二字节是怎么组成的呢?
首先类里面有一个虚函数表指针_vfptr

只要有虚函数就会存在虚表指针!这个是实现多态的关键之处!

增加几个函数调试一下

Base里面有虚函数,存了一个虚表指针,这个指针指向虚函数表(函数指针数组),该表存储着虚函数的地址

用例子结合虚函数表解释一下:

class Person {
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
protected:
	string _name;
};
class Student : public Person {
public:
	virtual void BuyTicket() { cout << "买票-打折" << endl; }
protected:
	int _id;
};
class Soldier : public Person {
public:
	virtual void BuyTicket() { cout << "买票-优先" << endl; }
protected:
	string _codename;//代号
};
void Func(Person* ptr)
{
	// 这⾥可以看到虽然都是Person指针Ptr在调用BuyTicket 
	// 但是跟ptr没关系,⽽是由ptr指向的对象决定的。 
	ptr->BuyTicket();
}
int main()
{
	// 其次多态不仅仅发生在派生类对象之间,多个派生类继承基类,重写虚函数后 
	// 多态也会发生在多个派生类之间。 
	Person ps;
	Student st;
	Soldier sr;
	Func(&ps);
	Func(&st);
	Func(&sr);
	return 0;
}

如何实现指向谁调用谁呢??

那么如何实现传基类调用基类的虚函数,传派生类调用派生类的虚函数
当然是使用切片了!

1. 首先每个实例化的类(如果有虚函数)会有一个虚函数表。
2. 传基类调用基类的虚函数,就正常在基类虚表中寻找其对应函数
3. 传派生类,因为多态函数是基类的指针,那么就会切片出来一个基类部分(虚函数表是派生类的),那么就会在派生类虚表调用对应虚函数。

汇编层次是如何表现的呢?具体的汇编语言实现还是比较直白的。

动态绑定与静态绑定:

  • 对不满足多态条件(指针或者引用+调用虚函数)的函数调用是在编译时绑定,也就是编译时确定调用函数的地址,叫做静态绑定。
  • 满足多态条件的函数调用是在运行时绑定,也就是在运行时到指向对象的虚函数表中找到调用函数的地址,叫做动态绑定。
// ptr是指针+BuyTicket是虚函数满⾜多态条件。 
 // 这⾥就是动态绑定,编译在运⾏时到ptr指向对象的虚函数表中确定调⽤函数地址 
 ptr->BuyTicket();

00EF2001 mov eax,dword ptr [ptr] 

00EF2004 mov edx,dword ptr [eax] 

00EF2006 mov esi,esp 

00EF2008 mov ecx,dword ptr [ptr] 

00EF200B mov eax,dword ptr [edx] 

00EF200D call eax
 // BuyTicket不是虚函数,不满⾜多态条件。 
 // 这⾥就是静态绑定,编译器直接确定调⽤函数地址 
 ptr->BuyTicket();

00EA2C91 mov ecx,dword ptr [ptr] 

00EA2C94 call Student::Student (0EA153Ch)

可以对比汇编层次理解分析

4.2虚函数表  

class Base {
public:
	virtual void func1() { cout << "Base::func1" << endl; }
	virtual void func2() { cout << "Base::func2" << endl; }
	void func5() { cout << "Base::func5" << endl; }
protected:
	int a = 1;
};
class Derive : public Base
{
public:
	// 重写基类的func1 
	virtual void func1() { cout << "Derive::func1" << endl; }
	virtual void func3() { cout << "Derive::func1" << endl; }//派生类自己的虚函数
	void func4() { cout << "Derive::func4" << endl; }
protected:
	int b = 2;
};

int main()
{
	Base b1;
	Base b2;
	Derive d;
	return 0;
}
  • 基类对象的虚函数表中存放基类所有虚函数的地址。(同类型对象虚表共用,不同类型对象虚表各自独立)
  • 派生类由两部分构成,继承下来的基类和自己的成员,一般情况下,继承下来的基类中有虚函数表指针,自己就不会再生成虚函数表指针。但是要注意的这里继承下来的基类部分虚函数表指针和基类对象的虚函数表指针不是同⼀个,就像基类对象的成员和派生类对象中的基类对象成员也是独立的。
  • 派生类中重写的基类的虚函数,派生类的虚函数表中对应的虚函数(与基类相同)就会被覆盖成派生类重写的虚函数地址。
  • 派生类的虚函数表中包含,基类的虚函数地址,派生类重写的(覆盖的)虚函数地址,派生类自己的虚函数地址三个部分。
  • 虚函数表本质是⼀个存虚函数指针的指针数组,一般情况这个数组最后面放了一个0x00000000标记。(这个C++并没有进行规定,各个编译器自行定义的,vs系列编译器会再后面放个0x00000000 标记,g++系列编译不会放)

虚函数和虚函数表存放在哪里呢??

注意:虚函数和普通函数⼀样的,编译好后是⼀段指令,都是存在代码段的,只是虚函数的地址又存到了虚函数表中。

那么虚函数表存放在哪呢?这个问题严格说并没有标准答案 C++标准并没有规定,我们可以验证一下:

class Base {
public:
	virtual void func1() { cout << "Base::func1" << endl; }
	virtual void func2() { cout << "Base::func2" << endl; }
	void func5() { cout << "Base::func5" << endl; }
protected:
	int a = 1;
};
class Derive : public Base
{
public:
	// 重写基类的func1 
	virtual void func1() { cout << "Derive::func1" << endl; }
	virtual void func3() { cout << "Derive::func1" << endl; }
	void func4() { cout << "Derive::func4" << endl; }
protected:
	int b = 2;
};
int main()
{
	int i = 0;
	static int j = 1;
	int* p1 = new int;
	const char* p2 = "xxxxxxxx";
	printf("栈:%p\n", &i);
	printf("静态区:%p\n", &j);
	printf("堆:%p\n", p1);
	printf("常量区:%p\n", p2);
	Base b;
	Derive d;
	Base* p3 = &b;
	Derive* p4 = &d;
	printf("Person虚表地址:%p\n", *(int*)p3);
	printf("Student虚表地址:%p\n", *(int*)p4);
	printf("虚函数地址:%p\n", &Base::func1);
	printf("普通函数地址:%p\n", &Base::func5);
	return 0;
}

这里是怎么拿到虚表地址的呢?

强制转换为 int*之后再解引用拿到前四个字节

对比一下可以看到虚表地址与常量区最接近,那可以推断出虚表储存在常量区!!!(vs2022编译环境)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

光伏场地建设规划 - 华为OD统一考试(E卷)

2024华为OD机试&#xff08;C卷D卷E卷&#xff09;最新题库【超值优惠】Java/Python/C合集 题目描述 祖国西北部有一片大片荒地&#xff0c;其中零星的分布着一些湖泊&#xff0c;保护区&#xff0c;矿区;整体上常年光照良好&#xff0c;但是也有一些地区光照不太好。某电力公…

C++中模板的初级使用函数模板(刚刚接触模板概念的小白也能明白)

文章目录 模板分类函数模板函数模板的原理函数模板基本语法 —— typename 以及 class简单的函数模板多类型模板参数class 和 typename 的选择类模板 模板分类 模板的核心思想是让编译器在编译时生成适用于具体类型的代码&#xff0c;这个过程称为模板实例化。C 中的模板分为两…

Sublime Text 3 相关设置

打开设置 { “font_size”: 16, // 字体大小 “save_on_focus_lost”: true, // 自动保存 }

射击靶标检测系统源码分享

射击靶标检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vis…

【吊打面试官系列-MySQL面试题】LIKE 声明中的%和_是什么意思?

大家好&#xff0c;我是锋哥。今天分享关于【LIKE 声明中的&#xff05;和_是什么意思&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; LIKE 声明中的&#xff05;和_是什么意思&#xff1f; &#xff05;对应于 0 个或更多字符&#xff0c;_只是 LIKE 语句中的…

Amazon Bedrock 模型微调实践(二):数据准备篇

本博客内容翻译自作者于 2024 年 9 月在亚马逊云科技开发者社区发表的同名博客&#xff1a; “Mastering Amazon Bedrock Custom Models Fine-tuning (Part 2): Data Preparation for Fine-tuning” 亚马逊云科技开发者社区为开发者们提供全球的开发技术资源。这里有技术文档、…

Leetcode—322. 零钱兑换【中等】(memset(dp,0x3f, sizeof(dp))

2024每日刷题&#xff08;159&#xff09; Leetcode—322. 零钱兑换 算法思想 dp实现代码 class Solution { public:int coinChange(vector<int>& coins, int amount) {int m coins.size();int n amount;int dp[m 1][n 1];memset(dp, 0x3f, sizeof(dp));dp[0][…

Django ORM(多表)

文章目录 前言一、关联关系模型二、一对多写入数据二、多对多写入数据二、跨表查询1.查找test 标签的文章2.查找作者名为 test 的文章及标签 三、跨表删除 前言 表与表之间的关系可分为以下三种&#xff1a; 一对一: 一对一关系表示一个模型的每个实例与另一个模型的每个实例…

【字符函数】strcpy函数(字符串复制函数)+strcat函数(字符串追加)+strcmp函数(字符串比较)【笔记】

1.复制函数--------------strcpy函数 函数使用 char*strcpy&#xff08;char* destination, const char* source&#xff09; strcpy函数用于拷贝字符串&#xff0c;即将一个字符串中的内容拷贝到另一个字符串中&#xff08;会覆盖原字符串内容&#xff09;。它的参数是两个指…

Mysql梳理6——order by排序

目录 6 order by排序 6.1 排序数据 6.2 单列排序 6.3 多行排列 6 order by排序 6.1 排序数据 使用ORDER BY字句排序 ASC&#xff08;ascend&#xff09;:升序DESC(descend):降序 ORDER BY子句在SELECT语句的结尾 6.2 单列排序 如果没有使用排序操作&#xff0c;默认…

【HarmonyOS NEXT】DevEco快速实现真机截屏,并保存到电脑

点日志点照机图标选一个路径保存图片在ide中右键图片&#xff0c;点复制电脑随便找个位置保存图片https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-screenshot-V5

1-2.Jetpack 之 Navigation 跳转编码模板

一、Navigation 1、Navigation 概述 Navigation 是 Jetpack 中的一个重要成员&#xff0c;它主要是结合导航图&#xff08;Navigation Graph&#xff09;来控制和简化 Fragment 之间的导航&#xff0c;即往哪里走&#xff0c;该怎么走 2、Navigate 引入 在模块级 build.gra…

Datawhale------Tiny-universe学习笔记——Qwen(1)

1. Qwen整体介绍 对于一个完全没接触过大模型的小白来说&#xff0c;猛一听这个名字首先会一懵&#xff1a;Qwen是啥。这里首先解答一下这个问题。下面是官网给出介绍&#xff1a;Qwen是阿里巴巴集团Qwen团队研发的大语言模型和大型多模态模型系列。其实随着大模型领域的发展&a…

全同台加密综述

文章目录 一、FHE的定义与性质1、核心算法2、性质 二、构造思想三、全同态加密研究进展1、支持部分同态的 Pre-FHE 方案2、基于理想格的 第1代 FHE方案3、基于LWE的 第2代 FHE方案3、基于近似特征向量的 第3代 FHE方案4、支持浮点数运算的 第4代 FHE方案5、其他 FHE方案5.1、基…

数字化时代,住宅代理是怎样为企业赋能的?

在数字化时代&#xff0c;企业的发展也面临着转型&#xff0c;一方面是未知的挑战&#xff0c;一方面是不可多得的机遇。如何在全球市场中保持竞争力是企业要认真思考的问题。如果说主动寻找出路太过冒险&#xff0c;那不妨试试内省式的自我管理革新。代理服务器是一种中介服务…

TI DSP下载器XDS100 V2.0无法使用问题

前言 TI DSP下载器XDS100 V2.0用着用着会突然报Error&#xff0c;特别是你想要用Code Composer Studio烧录下载程序的时候 查看设备管理器&#xff0c;发现XDS100 V2.0的设备端口莫名其妙消失了 问了淘宝的厂家&#xff0c;他说TI的开发板信号可能会导致调试器通信信号中断&a…

软件安全最佳实践:首先关注的地方

尽管组织拥有大量可用的工具&#xff0c;但应用程序安全性仍然不足。 最近的数据显示&#xff0c;在过去四到五年中&#xff0c;软件供应链攻击同比增长了 600-700%&#xff0c;超过一半的美国企业在过去 12 个月中遭受过某种形式的软件供应链攻击。 为何应用程序安全工作未…

相亲交易系统源码详解与开发指南

随着互联网技术的发展&#xff0c;越来越多的传统行业开始寻求线上转型&#xff0c;其中就包括婚恋服务。传统的相亲方式已经不能满足现代人快节奏的生活需求&#xff0c;因此&#xff0c;开发一款基于Web的相亲交易系统显得尤为重要开发者h17711347205。本文将详细介绍如何使用…

WEB攻防-JavaWweb项目JWT身份攻击组件安全访问控制

知识点&#xff1a; 1、JavaWeb常见安全及代码逻辑&#xff1b; 2、目录遍历&身份验证&逻辑&JWT&#xff1b; 3、访问控制&安全组件&越权&三方组件&#xff1b; 演示案例&#xff1a; JavaWeb-WebGoat8靶场搭建使用 安全问题-目录遍历&身份认…

Ubuntu20.04 搜索不到任何蓝牙设备

电脑信息 联想扬天YangTianT4900k 问题描述 打开蓝牙之后&#xff0c;一直转圈&#xff0c;搜索不到任何蓝牙设备 排查 dmesg | grep -i blue 有如下错误&#xff1a; Bluetooth: hci0: RTL: unknown IC info, lmp subver 8852, hci rev 000b, hci ver 000b lsusb 芯片型号如…