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

news2024/9/22 21:54:45

🌈个人主页:秋风起,再归来~
🔥系列专栏: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、完结散花

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

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

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

我们下期不见不散~~

​​​​

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

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

相关文章

数据结构-树和二叉树

树 和 二叉树 1.树的概念 树 tree 是n(n>0)个节点的有限集 在任意的一个非空树中 (1)有且仅有一个特定的被称为 根(root) 的节点 (2)当n>1时, 其余的节点可分为m(m>0)个互不相交的有限集T1, T2, T3, .... …

Java 入门指南:JVM(Java虚拟机)—— Java 类加载器详解

类加载器 类加载器&#xff08;Class Loader&#xff09;是 Java 虚拟机&#xff08;JVM&#xff09;的一部分&#xff0c;它的作用是将类的字节码文件&#xff08;.class 文件&#xff09;从磁盘或其他来源加载到 JVM 中。类加载器负责查找和加载类的字节码文件&#xff0c;并…

HTML和CSS做一个无脚本的手风琴页面(保姆级)

一、前言 使用HTML和CSS做一个无脚本的手风琴页面。让知识以自己喜欢的方式进入脑子&#xff0c;适用于很多场景&#xff0c;比如以下&#xff1a; 【注&#xff1a;图片源自百度】 二、HTML框架 使用外部样式表&#xff0c;将CSS文件用link标签引入 整体框架如下图&#x…

基于微信小程序的游泳馆管理系统--论文源码调试讲解

2 关键技术介绍 2.1 SSM框架 开发信息管理系统的主流框架是SSM&#xff08;Spring Spring MVC MyBatis&#xff09;&#xff0c;SSM框架web层使用Spring MVC框架&#xff0c;使传输前后端数据变得简单&#xff1b;对于业务层使用Spring作为轻量级控制反转和面向切面的容器框…

Leetcode面试经典150题-39.组合总数

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target &#xff0c;找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 &#xff0c;并以列表形式返回。你可以按 任意顺序 返回这些组合。 candidates 中的 同一个 数字可以 无限制重复被选取 。如…

基于Ambari搭建hadoop生态圈+Centos7安装教程

当我们学习玩搭建hadoop的时候&#xff0c;未免也会遇见很多繁琐的事情&#xff0c;比如很多错误&#xff0c;需要解决。在以后公司&#xff0c;也不可能让你一个一个搭建hadoop&#xff0c;成千上万的电脑&#xff0c;你在一个个搭建&#xff0c;一个个报错&#xff0c;而且每…

深度学习与应用:人体关键点检测

实验二 深度学习与应用&#xff1a;人体关键点检测 1、 实验目的 了解人体关键点检测基础流程熟悉YOLOV7-pose模型结构掌握 YOLOv7-pose 模型的训练、Fine-tuning 以及推理的能力掌握YOLOV7-pose模型对实际问题的应用能力&#xff0c;了解如何在特定的场景和任务中应用该模型…

为什么git有些commit记录,只有git reflog可以看到,git log看不到?

文章目录 原因分析1. git log 只能显示 **可达的** 提交2. git reflog 记录所有引用的变更 常见导致 git log 看不到提交的原因1. git reset 操作2. git rebase 操作3. 分支删除4. git commit --amend5. 垃圾回收&#xff08;GC&#xff09;* 如何恢复 git log 看不到的提交&am…

QT实现升级进度条页面

一.功能说明 在Qt中实现固件升级的进度条显示窗口&#xff0c;你可以通过创建一个自定义的对话框&#xff08;Dialog&#xff09;来完成。这个对话框可以包含一个进度条&#xff08;QProgressBar&#xff09;、一些文本标签&#xff08;QLabel&#xff09;用于显示状态信息&am…

学生管理系统1.0版本

学生管理系统1.0版本有5个功能&#xff0c;即添加学生、删除学生、修改学生、查看全部学生、退出系统。 里面对添加重复学号、删除和修改不存在的学号等问题都有相应的解决办法。 代码区&#xff1a; Student.java package student;//快捷键Altinsert public class Student …

iOS 消息机制详解

应用 解决NSTimer、CADisplayLink循环引用。 二者都是基于runloop的定时器&#xff0c;由于处理事件内容不一样&#xff0c;runloop 每运行一次运行耗时就不一样&#xff0c;无法准确的定时触发timer的事件。 NSProxy 与 NSObject 如果继承自NSProxy 直接开始消息转发&…

【comfyUI工作流】一键生成专属欧美漫画!

现在你不需要在webui上手动设置一堆的参数 来将自己的照片转绘成欧美漫画插画 可以通过我制作的工作流一键完成转绘&#xff0c;更加效率便捷&#xff0c; 而且不需要你懂什么专业的AI绘画知识&#xff0c;会打开工作流&#xff0c;上传图片就可以 工作流特点 真实照片一键…

linux StarRocks 安装

一、检查服务器是否支持avx2&#xff0c;如果执行命令显示空&#xff0c;则不支持&#xff0c;那么安装后无法启动BE cat /proc/cpuinfo |grep avx2我的支持显示如下&#xff1a; 二、安装 docker run -p 9030:9030 -p 8030:8030 -p 8040:8040 -p 9001:9000 --privilegedtrue…

开源PHP导航网源码/精美简约网址导航收录网站/QQ技术导航程序

源码简介&#xff1a; 一款给力的开源PHP导航网源码&#xff0c;它不仅外观精美简约&#xff0c;还是个网址导航收录网站/QQ技术导航程序哦&#xff01; 在信息爆炸的时代&#xff0c;找网页就像大海捞针一样难。但是有了像PHP 导航网这样的神器&#xff0c;一切都变得简单了…

Gin框架入门(1)--路由搭建与Json处理

背景知识 为什么要使用Go框架 如果不使用框架&#xff0c;在创建服务器和调用端口时会遇到各种各样“奇怪”的问题&#xff08;就是出错的排查方向可能达到十几种&#xff09;&#xff0c;而且这些问题很难有相似性。同时作为适应于微服务的一门语言&#xff0c;代码的规范化…

【计算机组成原理】主存储器深度解析

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文由 JohnKi 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f4e2;未来很长&#…

基于SpringBoot+Vue的时尚美妆电商网站系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 精品专栏&#xff1a;Java精选实战项目源码、Python精…

Windows下安装Neo4j流程

Neo4j简介 Neo4j 是一个基于图形结构的 NoSQL 数据库&#xff0c;专门用于存储和管理图数据。与传统的关系型数据库不同&#xff0c;Neo4j 使用 图&#xff08;graph&#xff09;的形式来表示数据&#xff0c;其中数据点&#xff08;称为 节点&#xff09;通过 边&#xff08;…

GUI编程19:贪吃蛇小游戏及GUI总结

视频链接&#xff1a;21、贪吃蛇之界面绘制_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1DJ411B75F?p21&vd_sourceb5775c3a4ea16a5306db9c7c1c1486b5 1.游戏中用的的图片素材 1.贪吃蛇游戏的主启动类StartGame&#xff1b; package com.yundait.snake;import j…

【ArcGISPro】配置模块

ArcGIS Pro 配置类似于加载项&#xff0c;但提供了扩展应用程序的其他方法。它可以帮助您设计更贴近您组织品牌和工作流的 ArcGIS Pro 版本。 托管配置是比 Add-in 更高级别的自定义。 配置可以提高加载项安全级别并添加非管理员指定的已知文件夹。 配置可以提供比插件更广泛…