C++从入门到起飞之——多态 全方位剖析!

🌈个人主页:秋风起,再归来~
🔥系列专栏:C++从入门到起飞          
🔖克心守己,律己则安

目录

1. 多态的概念

2. 多态的定义及实现

2.1 多态的构成条件

2.1.1 实现多态还有两个必须重要条件:

2.1.2 虚函数

2.1.3 虚函数的重写/覆盖

2.1.4 多态场景的⼀个选择题

2.1.5 虚函数重写的⼀些其他问题

 2.1.6 override和final关键字

2.1.7 重载/重写/隐藏的对⽐

3. 纯虚函数和抽象类

4. 多态的原理 

 4.1 虚函数表指针

4.2 多态的原理

4.2.1 多态是如何实现的

4.2.2 动态绑定与静态绑定

4.3 虚函数表

5、完结散花


1. 多态的概念

多态(polymorphism)的概念:通俗来说,就是多种形态

多态分为编译时多态(静态多态)运⾏时多 态(动态多态)。

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

运⾏时多态,具体点就是去完成某个⾏为(函数),可以传不同的对象就会完成不同的⾏为,就达到多种形态。⽐如买票这个⾏为,当普通⼈买票时,是全价买票;学⽣买票时,是优惠买票(5折或75折);军 ⼈买票时是优先买票。再⽐如,同样是动物叫的⼀个⾏为(函数),传猫对象过去,就是”喵喵“,传狗对象过去,就是"汪汪"。

2. 多态的定义及实现

2.1 多态的构成条件

多态是⼀个继承关系的下的类对象,去调⽤同⼀函数,产⽣了不同的⾏为。⽐如Student继承了 Person。Person对象买票全价,Student对象优惠买票。

2.1.1 实现多态还有两个必须重要条件:

• 必须基类指针或者引⽤调⽤虚函数

• 被调⽤的函数必须是虚函数。 

说明:要实现多态效果,第⼀必须是基类的指针或引⽤调用虚函数,因为只有基类的指针或引⽤才能既指向基类对象,又指向派⽣类对象;第⼆派⽣类必须对基类的虚函数重写/覆盖,重写或者覆盖了,派⽣类才能有不同的函数,多 态的不同形态效果才能达到。

这里我先用代码来感受一下运行时多态:

//父类
class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "普通人买票全价!" << endl;
	}
protected:
};
//学生类(子类)
class Student:public Person
{
public:
	virtual void BuyTicket()
	{
		cout << "学生买票半价!" << endl;
	}
protected:
};


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

//void fun(Person* p)
//{
//	p->BuyTicket();
//}

int main()
{
	Person p1;
	Student s1;
	fun(p1);
	fun(s1);
	
	return 0;
}

我们可以看到上面这段程序,我们通过传递不同的类型的对象调用同一个函数(执行同一种行为),但是他们所表现出来的结果却是不同的。这就是动态多态(运行时多态)!

2.1.2 虚函数

类成员函数前⾯加virtual修饰,那么这个成员函数被称为虚函数。注意⾮成员函数不能加virtual修 饰。 

class Person 
{
public:
 virtual void BuyTicket() { cout << "买票-全价" << endl;}
};

注意:这里用关键字virtual修饰成员函数和之前讲的继承时用virtual修饰从而达到虚继承这两者是没有任何关联的!

2.1.3 虚函数的重写/覆盖

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

注意:在重写基类虚函数时,派⽣类的虚函数在不加virtual关键字时,虽然也可以构成重写(因为继承后基类的虚函数被继承下来了在派⽣类依旧保持虚函数属性),但是该种写法不是很规范,不建议这样 使⽤,不过在考试选择题中,经常会故意买这个坑,让你判断是否构成多态。

2.1.4 多态场景的⼀个选择题

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

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

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()
{
	B* p = new B;
	p->test();
	return 0;
}

详解: 首先我们来思考一下,p在调用test函数时有没有构成多态。我们先来看看在继承体系下,有没有实现多态的前提条件,即子类是否完成了父类虚函数的重写!虽然子类的函数没有用virtual修饰,但这并不重要,重要的是子类里面的缺省值和父类并不相同,那这构不构成重写呢?我们还是回到重写的定义三同(返回值类型相同,函数名相同,参数列表相同)注意,我们所说的参数列表形同是指形参的类型、顺序和个数相同!与形参的缺省值是否相同并没有关系,甚至和形参名是否相同也没有关系!

所以这里得出结论,这里具有实现多态的前提条件。

那我们再来看看,以上程序是否使用了基类的指针或引用调用虚函数

我们new了一个子类对象,然后再用子类的指针去调用test函数,我们知道test函数是被子类从父类继承下来的。之前我们学了继承都知道,(子类继承父类后,会把父类的所有成员继承下来)这是形象的说法,实际上子类真的会把父类的成员函数拷贝一份放到子类当中吗?当然不会!我们在调用子类继承下来的成员函数时,实际上是去父类当中调用相应的函数(如果没有构成隐藏)。

而父类的test函数中隐藏了形参(A* this)是父类的指针!this->fun();即满足了基类的指针或者引用调用虚函数这个条件!

综上:p在调用test函数时有构成多态!

 那这个题目的答案是不是B->0呢?  我们运行一下看看结果:

然而结果却出乎我们的意料!怎么回事呢?

这里我就要再讲一下重写的本质了: 重写的本质是对父类虚函数的函数体的重写!

说明:在继承体系下,子类的成员函数一旦对父类的虚函数构成重写(满足三同),那么子类的成员函数除函数体外其他内容(子类缺省值失效)被父类完全覆盖!这也说明了为什么重写(重写函数体)又叫做覆盖!这也解释了为什么在子类中成员函数不用加virtual依然构成重写!因为继承后基类的虚函数被继承下来了在派⽣类依旧保持虚函数属性。

再思考一下以下程序的结果:

int main()
{
	A* p = new B;
	p->test();
	return 0;
}

 p调用的不应该是A类的func吗,即使构成多态,不应该是不同类型的对象调用不同的函数吗?为什么这里还是调到了B类重写的函数。这和继承当中讲的父类和派生类间的转换有关当我们把子类对象赋值给父类的指针或者引用时,这里有个形象的说法叫切片或者切割。寓意把子类中父类那部分切出来,父类的指针或者引用指向的是子类中切出来的基类的那部分。(这也进一步解释了:要实现多态效果,第⼀必须是基类的指针或引⽤调用虚函数,因为只有基类的指针或引⽤才能既指向基类对象,又指向派⽣类对象

所以这时A*类型的p还是指向B类型的对象,指向谁调用谁,所以我们还是调用到了B!

2.1.5 虚函数重写的⼀些其他问题

• 协变(了解)

 派⽣类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引 ⽤,派⽣类虚函数返回派⽣类对象的指针或者引⽤时,称为协变。协变的实际意义并不⼤,所以我们 了解⼀下即可。

class A {};
class B : public A {}; 

class Person {
public:
	virtual A* BuyTicket()
	{
		cout << "买票-全价" << endl;
		return nullptr;
	}
};

class Student : public Person {
public:
	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;
}

• 析构函数的重写

基类的析构函数为虚函数,此时派⽣类析构函数只要定义,⽆论是否加virtual关键字,都与基类的析 构函数构成重写,虽然基类与派⽣类析构函数名字不同看起来不符合重写的规则,实际上编译器对析 构函数的名称做了特殊处理,编译后析构函数的名称统⼀处理成destructor,所以基类的析构函数加了vialtual修饰,派⽣类的析构函数就构成重写

注意:这个问题⾯试中经常考察,⼤家⼀定要结合类似下⾯的样例才能讲清楚,为什么基类中的析构函数建议设计为虚函数。  

class A
{
public:
	virtual ~A()
	{
		cout << "~A()" << endl;
	}
};
class B : public A {
public:
	~B()
	{
		cout << "~B()->delete:" << _p << endl;
		delete _p;
	}
protected:
	int* _p = new int[10];
};
// 只有派⽣类Student的析构函数重写了Person的析构函数,下⾯的delete对象调⽤析构函数,才能
//构成多态,才能保证p1和p2指向的对象正确的调⽤析构函数。
int main()
{
	A* p1 = new A;
	A* p2 = new B;
	delete p1;
	delete p2;
	return 0;
}

如果我们在父类当中的析构函数中不加比virtual,则子类和父类的析构函数不构成重写!所以我们在析构p2时并不会发生多态,从而导致B部分无法完成析构!如果B中有资源的申请,还会发生内存泄漏的问题! 

 

 2.1.6 override和final关键字

从上⾯可以看出,C++对函数重写的要求⽐较严格,但是有些情况下由于疏忽,⽐如函数名写错参数写 错等导致⽆法构成重写,⽽这种错误在编译期间是不会报出的,只有在程序运⾏时没有得到预期结果 才来debug会得不偿失,因此C++11提供了override,可以帮助⽤⼾检测是否重写。如果我们不想让派 ⽣类重写这个虚函数,那么可以⽤final去修饰。

// error C3668: “Benz::Drive”: 包含重写说明符“override”的⽅法没有重写任何基类⽅法 
class Car {
public:
	virtual void Dirve()
	{}
};
class Benz :public Car {
public:
	virtual void Drive() override { cout << "Benz-舒适" << endl; }
};
int main()
{
	return 0;
}

// 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; }
};
int main()
{
	return 0;
}

2.1.7 重载/重写/隐藏的对⽐

注意:这个概念对⽐经常考,⼤家得理解记忆⼀下 

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;
}

4. 多态的原理 

 4.1 虚函数表指针

下⾯编译为32位程序的运⾏结果是什么()

A.编译报错         B.运⾏报错        C.8         D.12

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;
}

上⾯题⽬运⾏结果12bytes,除了_b和_ch成员,还多⼀个__vfptr放在对象的前⾯(注意有些平台可能 会放到对象的最后⾯,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代 表function)。⼀个含有虚函数的类中都⾄少都有⼀个虚函数表指针(简称虚表指针),因为⼀个类所有虚函数的地址要 被放到这个类对象的虚函数表中,虚函数表也简称虚表。 

虚函数表实际上就是存储虚函数地址的一个数组,也就是函数指针数组! 

4.2 多态的原理

4.2.1 多态是如何实现的

从底层的⻆度Func函数中ptr->BuyTicket(),是如何作为ptr指向Person对象调用Person::BuyTicket, ptr指向Student对象调⽤Student::BuyTicket的呢?

通过下图我们可以看到,满⾜多态条件后,底层不再是编译时通过调⽤对象确定函数的地址,⽽是运⾏时到指向的对象的虚表中确定对应的虚函数的地址,这样就实现了指针或引⽤指向基类就调⽤基类的虚函数指向派⽣类就调⽤派⽣类对应的虚函 数。第⼀张图,ptr指向的Person对象,调⽤的是Person的虚函数;第⼆张图,ptr指向的Student对 象,调⽤的是Student的虚函数。

class Person {
public:
	virtual void BuyTicket() 
	{ 
		cout << "买票-全价" << endl; 
	}
};
class Student : public Person {
public:
	virtual void BuyTicket() 
	{ 
		cout << "买票-打折" << endl; 
	}
};
class Soldier : 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;
	Soldier sr;
	Func(&ps);
	Func(&st);
	Func(&sr);
	return 0;
}

4.2.2 动态绑定与静态绑定

 • 对不满⾜多态条件(指针或者引⽤+调⽤虚函数)的函数调⽤是在编译时绑定,也就是编译时确定调⽤函数的地址,叫做静态绑定

• 满⾜多态条件的函数调⽤是在运⾏时绑定,也就是在运⾏时到指向对象的虚函数表中找到调⽤函数的地址,也就做动态绑定。 

4.3 虚函数表

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 b;
	Derive d;
	return 0;
}

如下所有演示都以上面代码显示: 

• 基类对象的虚函数表中存放基类所有虚函数的地址

• 派⽣类由两部分构成,继承下来的基类⾃⼰的成员,⼀般情况下,继承下来的基类中有虚函数表指针,⾃⼰就不会再⽣成虚函数表指针。但是要注意的这⾥继承下来的基类部分虚函数表指针基类对象的虚函数表指针不是同⼀个,就像基类对象的成员和派⽣类对象中的基类对象成员也独⽴ 的。

• 派⽣类中重写的基类的虚函数,派⽣类的虚函数表中对应的虚函数就会被覆盖成派⽣类重写的虚函数地址。

• 派⽣类的虚函数表中包含,基类的虚函数地址派⽣类重写的虚函数地址派⽣类⾃⼰的虚函数地址三个部分。

虚函数表本质是⼀个存虚函数指针的指针数组,⼀般情况这个数组最后⾯放了⼀个0x00000000标 记。(这个C++并没有进⾏规定,各个编译器⾃⾏定义的,vs系列编译器会再后⾯放个0x00000000 标记,g++系列编译不会放)

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

虚函数表存在哪的?这个问题严格说并没有标准答案C++标准并没有规定,我们写下⾯的代码可以 对⽐验证⼀下。vs下是存在代码段(常量区)


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", (void*)*(int*)p3);
	printf("Student虚表地址:%p\n", (void*)*(int*)p4);
	printf("虚函数地址:%p\n", &Base::func1);
	printf("普通函数地址:%p\n",&Base::func5);
	return 0;
}

下面我们再来验证一下在Linux下虚函数表在哪里?

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",*(double*)p3);
        printf("Student虚表地址:%p\n", *(double*)p4);
        printf("虚函数地址:%p\n", (void*)&Base::func1);
        printf("普通函数地址:%p\n",(void*)&Base::func5);
        return 0;
}

 注意:Linux下默认是64位,指针大小为8个字节,所以我们要把int换位double取8个字节!

 我们观察可以看到Linux(Ubuntu)下的虚表地址是在堆区上的!

5、完结散花

好了,这期的分享到这里就结束了~

如果这篇博客对你有帮助的话,可以用你们的小手指点一个免费的赞并收藏起来哟~

如果期待博主下期内容的话,可以点点关注,避免找不到我了呢~

我们下期不见不散~~

​​​​

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

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

相关文章

群晖NAS使用Docker本地部署网页版Ubuntu系统并实现无公网IP远程访问

文章目录 前言1. 下载Docker-Webtop镜像2. 运行Docker-Webtop镜像3. 本地访问网页版Linux系统4. 群晖NAS安装Cpolar工具5. 配置异地访问Linux系统6. 异地远程访问Linux系统7. 固定异地访问的公网地址 前言 本文旨在详细介绍如何在群晖NAS部署docker-webtop&#xff0c;并结合c…

通用接口开放平台设计与实现——(31)API服务线程安全问题确认与修复

背景 在本系列的前面一篇博客评论中&#xff0c;有小伙伴指出&#xff0c;API服务存在线程安全问题&#xff1a; https://blog.csdn.net/seawaving/article/details/122905199#comments_34477405 今天来确认下&#xff0c;线程是否安全&#xff1f;如不安全&#xff0c;如何…

高配小主机加装SSD固态硬盘,我选择性能与设计兼备的希捷酷鱼 530

高配小主机加装SSD固态硬盘&#xff0c;我选择性能与设计兼备的希捷酷鱼 530 哈喽小伙伴们好&#xff0c;我是Stark-C~ 我最近入手了零刻的一款新发布的 GTi12 Ultra高性能迷你主机&#xff0c;其出色的配置与强大的功能让我有了将它用作主力机的打算。不过因为它的高配版本搭…

【记录一下VMware上开虚拟端口映射到公网】

材料 win11 和装在vmware上的ubuntu 步骤一在Ubuntu上配置静态地址&#xff0c;配置如下 vim /etc/netplan/01-network-manager-all.yaml(此文件看系统上对应的是哪个文件&#xff0c;建议先备份)network:version: 2renderer: NetworkManagerethernets:ens33:dhcp4: falseadd…

四十一、完成内容添加功能(使用go测试方法)

目录 一、添加model 二、完成相关dao 三、使用测试类进行测试 1、把光标防止要测试的方法上&#xff0c;右击并选择 2、自动会生成一个以dao文件加_test命名的文件 3、在其中完善方法并完成测试 四、完成content_create_handle 一、添加model 按数据库字段以及字段格式完…

Android 如何实现搜索功能:本地搜索?数据模型如何设计?数据如何展示和保存?

目录 效果图为什么需要搜索功能如何设计搜索本地的功能&#xff0c;如何维护呢&#xff1f;总结 一、效果图 二、为什么需要搜索功能 找一个选项&#xff0c;需要花非常多的时间&#xff0c;并且每次都需要指导客户在哪里&#xff0c;现在只要让他们搜索一下就可以。这也是模…

基于SpringBoot+Vue的剧本杀管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于JavaSpringBootVueMySQL的…

AIGC7: 高通骁龙AIPC开发者沙龙过程记录A

图中是一座高耸的宫殿。 就像AI的出现&#xff0c;慢慢初现端倪&#xff0c;头角峥嵘。 背景 一直以来都比较关注AI的发展&#xff0c;有幸再一次参加异常AI的盛会。 从我的角度看。 高通是一家生产芯片的公司&#xff0c;国内的小米&#xff0c;荣耀&#xff0c;Oppo , Vi…

华为为什么要做三折叠屏手机?

前些天我做了一条视频&#xff0c;关于讲华W的新的三折叠屏手机。我说我有点失望&#xff0c;结果引起了华W的同事的一些关注。于是&#xff0c;华W几位高管都跑过来&#xff0c;跟我解释为什么会出现这样的一个状态。 我才知道&#xff0c;这款手机他们其实是亏着钱在卖的。因…

【测试】——Selenium API (万字详解)

&#x1f4d6; 前言&#xff1a;本文详细介绍了如何利用Selenium进行Web自动化测试&#xff0c;包括定位元素&#xff08;如cssSelector和xpath&#xff09;、常用操作函数&#xff08;如点击、输入等&#xff09;、窗口管理、键盘鼠标事件和浏览器导航&#xff0c;以及处理弹窗…

图说GPT网络结构(参数量与计算量估计)

现在AI领域的主流模型几乎都是Transformer网络架构衍生而来。大热的LLM中的生成类模型很多都是来自于Transformer的变体&#xff0c;即decoder only架构。而GPT就是该类中的经典模型。尽管现在变体甚多&#xff0c;但大多没有根本性地改变其套路。 为了阐述方便&#xff0c;首…

音视频入门基础:AAC专题(8)——FFmpeg源码中计算AAC裸流AVStream的time_base的实现

音视频入门基础&#xff1a;AAC专题系列文章&#xff1a; 音视频入门基础&#xff1a;AAC专题&#xff08;1&#xff09;——AAC官方文档下载 音视频入门基础&#xff1a;AAC专题&#xff08;2&#xff09;——使用FFmpeg命令生成AAC裸流文件 音视频入门基础&#xff1a;AAC…

数据安全治理

数据安全治理 1.数据安全治理2.终端数据安全加密类权限控制类终端DLP类桌面虚拟化安全桌面 3.网络数据安全4.存储数据安全5.应用数据安全6.其他话题数据脱敏水印与溯源 7.UEBA8.CASB 1.数据安全治理 数据安全治理最为重要的是进行数据安全策略和流程制订。在企业或行业内经常发…

[大语言模型-论文精读] 以《黑神话:悟空》为研究案例探讨VLMs能否玩动作角色扮演游戏?

1. 论文简介 论文《Can VLMs Play Action Role-Playing Games? Take Black Myth Wukong as a Study Case》是阿里巴巴集团的Peng Chen、Pi Bu、Jun Song和Yuan Gao&#xff0c;在2024.09.19提交到arXiv上的研究论文。 论文: https://arxiv.org/abs/2409.12889代码和数据: h…

通过logstash同步elasticsearch数据

1 概述 logstash是一个对数据进行抽取、转换、输出的工具&#xff0c;能对接多种数据源和目标数据。本文介绍通过它来同步elasticsearch的数据。 2 环境 实验仅仅需要一台logstash机器和两台elasticsearch机器&#xff08;elasticsearch v7.1.0&#xff09;。本文用docker来模…

人工智能不是人工“制”能

文/孟永辉 如果你去过今年在上海举办的世界人工智能大会&#xff0c;就会知道当下的人工智能行业在中国是多么火爆。 的确&#xff0c;作为第四次工业革命的重要组成部分&#xff0c;人工智能愈发引起越来越多的重视。 不仅仅是在中国&#xff0c;当今世界的很多工业强国都在将…

HarmonyOS 速记

目录 装饰器Entry(入口)Component(组件)Builder(构建)State(状态)Prop(属性)Preview(预览)PreviewerInspector 结构体structbuild自定义组件自定义 Custom 组件 export(导出) & import(导入) Page(页面)生命周期aboutToAppear 数据Array(数组/集合)Map(映射) 容器&#xff…

Wireshark学习使用记录

wireshark 是一个非常好用的抓包工具&#xff0c;使用 wireshark 工具抓包分析&#xff0c;是学习网络编程必不可少的一项技能。 原理 Wireshark使用的环境大致分为两种:一种是电脑直连互联网的单机环境&#xff0c;另外一种就是应用比较多的互联网环境&#xff0c;也就是连接…

nginx模块篇(四)

文章目录 四、Nginx的扩展模块4.1. Lua4.1.1 概念4.1.2 特性4.1.3 应用场景4.1.4 Lua的安装4.1.5 Lua的语法4.1.5.1 第一个Lua程序4.1.5.2 Lua的注释4.1.5.3 标识符4.1.5.4 关键字4.1.5.5 运算符4.1.5.6 全局变量&局部变量4.1.5.7 Lua数据类型nilbooleannumberstringtablef…

Windows本地连接远程服务器并创建新用户详细记录

前提可知&#xff1a; &#xff08;1&#xff09;服务器IP地址&#xff1a;x.x.x.x &#xff08;2&#xff09;服务器名称&#xff1a;root&#xff08;一般默认为root&#xff0c;当然也有别的名称&#xff09; &#xff08;3&#xff09;服务器登陆密码&#xff1a;**** 一、…