多态(C++)

news2024/9/27 15:19:19

多态

  • 一、初识多态
    • 概念
    • “登场”
      • 1>. 多态的构成条件
      • 2>. 虚函数
      • 3>. 虚函数重写(覆盖)
      • 4>. 虚函数重写的两个例外
        • 1. 协变 一 基类和派生类虚函数返回值类型不同
        • 2. 析构函数重写(基类和派生类析构函数名不同)
    • 小结
  • 二、延伸知识
    • 1>. C++11 override和final
      • 拓展一最终类
    • 2>. 抽象类
      • 概念
      • 接口继承和实现继承
  • 三、原理
    • 1>. 虚函数表(也称虚表)
      • 引入
      • 分析虚表
    • 2>. 多态的原理
    • 3>. 拓展 一 静态绑定和动态绑定
  • 四、单继承和多继承的虚函数表
    • 1>. 单继承中的虚函数表
    • 2>. 多继承中的虚函数表
      • 1. 多继承
      • 2. 菱形继承
      • 3. 菱形虚拟继承

一、初识多态

概念

概念:去完成某个行为,当不同的对象去完成时会产生出不同的状态

eg:
买车票:普通成年人买票时,是全价票;学生买票时,是半价票;

“登场”

1>. 多态的构成条件

  1. 多态是在不同继承关系的类对象,去调用同一函数,产生不同的行为。
  2. 在继承的继承上,需要:
  • 必须通过基类的指针或者引用调用虚函数
  • 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写 (覆盖)

eg: “见见猪跑”
多态

2>. 虚函数

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

虚函数

3>. 虚函数重写(覆盖)

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

注意: 重写基类虚函数时,派生类的虚函数可以不加virtual关键字。
eg:
virtual

4>. 虚函数重写的两个例外

虚函数重写的要求是派生类虚函数与基类虚函数的返回值类型,函数名,参数类型完全相同(三同)。例外的原因就是不满足三同

1. 协变 一 基类和派生类虚函数返回值类型不同

协变:派生类重写基类虚函数时,与基类虚函数返回值类型不同。
满足协变的条件:返回值类型可以不同,但是返回值必须是父子关系的指针或引用

test code:

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

test code:

class Person
{
public:
	virtual ~Person()
	{
		cout << "~Person()" << endl;
	}
};

class Student : public Person
{
public:
	virtual ~Student()
	{
		cout << "~Student()" << endl;
	}
};


//只有派生类Student的析构函数重写了Person的析构函数,
//这时delete调用析构时,才能构成多态,保证正确调用析构函数
int main()
{
	Person* p1 = new Person;
	Person* p2 = new Student;

	delete p1;
	delete p2;
	return 0;
}

//output:
//~Person()
//~Student()
//~Person()

小结

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

二、延伸知识

1>. C++11 override和final

  1. override:检查派生类虚函数是否重写了基类某个虚函数,如果没有重写报错

test code:

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

class Benz : public Car
{
public:
	virtual void Drive() override   //ok  完成了重写
	{
		cout << "Benz-舒适" << endl;
	}
};
  1. final:修饰虚函数,表示该虚函数不能再被重写

test code:

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

class Benz : public Car
{
public:
	virtual void Drive()  //error  原因:final禁止了重写
	{
		cout << "Benz-舒适" << endl;
	}
};

拓展一最终类

当我们想设计不想被继承的类时,有两种方法

方法1 一一 对应C++98

eg1: 隐藏构造函数,当想要创建A对象时,定义一个静态的成员函数

class A
{
public:
	static A CreateObj()
	{
		return A();
	}
private:
	A()
	{}
};

class B : public A
{};

int main()
{
	//B bb;     //err
	A::CreateObj();
	return 0;
}

eg2:隐藏析构函数,当想要创建A对象new一个,释放时定义一个静态的destructor,即可

class A
{
public:
private:
	~A()
	{}
};
class B : public A
{};

int main()
{
	//B bb;   //err
	A* p = new A;
	return 0;
}

方法2 一一 对应C++11
eg:被final修饰的类,被称为最终类,不能被继承

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

2>. 抽象类

概念

  1. 纯虚函数:虚函数的后面写上 = 0
  2. 抽象类(接口类):包含纯虚函数的类。
    抽象类不能实例化出对象,派生类继承后也不能,只有重写纯虚函数,派生类才可以实例化对象。规范了派生类必须重写。

test code:

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()
{
	Car* pBenz = new Benz;
	pBenz->Drive();

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

接口继承和实现继承

  1. 实现继承:普通函数继承。派生类继承基类函数,可以使用,继承的是函数实现。
  2. 接口继承:虚函数的继承。派生类继承的是虚函数的接口,目的是为了重写,达成多态,继承的是接口。
    注意:不实现多态就不要把函数定义成虚函数

三、原理

基于vs2019进行模型分析

1>. 虚函数表(也称虚表)

引入

test code:

//计算Base对象的大小
class Base
{
public:
	virtual void Func()
	{
		cout << "Func()" << endl;
	}

private:
	int _b = 1;
};

int main()
{
	Base b;


	cout << sizeof(b) << endl;
	return 0;
}
//output: 8

代码分析:
模型分析

分析虚表

test code:

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

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

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

通过上面的测试代码,发现一下六点:

虚表

2>. 多态的原理

上面分析了很久的虚表,以对虚表的介绍为基础,来分析多态的原理。

test code:

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

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

int main()
{
	Person ps;
	Func(&ps);
	ps.BuyTicket();

	Student st;
	Func(&st);
	return 0;
}

达到多态,有两个条件:一是虚函数覆盖,一个是对象的指针或引用调用虚函数。
通过下面汇编代码的分析,看出满足多态以后的函数调用,不是在编译时确定的,是运行起来以后到对象中去找的。不满足多态的函数调用是编译时确认好的。

函数覆盖

对象的指针或引用

3>. 拓展 一 静态绑定和动态绑定

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

上一个汇编代码的例子,就很好的解释了静态绑定和动态绑定

四、单继承和多继承的虚函数表

1>. 单继承中的虚函数表

test code:

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

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

int main()
{
	Base b;
	Derive d;

	return 0;
}

观察下图中监视窗口,发现派生类的虚函数func3和func4看不见。 原因:编译器的监视窗口隐藏了这个两个函数。

查看虚表

在分析虚表这一小节内容时,在第四小点说到,虚表本质是一个存虚函数指针的指针数组,一般情况这个数组最后放一个nullptr,通过监视窗口查看不了派生类对象d的虚表,下面我们借用nullptr的帮助,使用代码打印出虚表中的函数

PrintVTable_code:打印虚表的代码

注意:这个打印虚表的代码经常崩溃,编译器对虚表的处理不干净,虚表最后没有放nullptr的指针,导致越界。我们只需要清理解决方案,重新编译即可。

typedef void(*VFPtr) ();  //函数指针
void PrintVTable(VFPtr VTable[])
{
	//依次取虚表中的虚函数指针打印并调用。
	cout << "虚表地址>" << VTable << endl;
	for (size_t i = 0; VTable[i] != nullptr; i++)
	{
		printf("第%d个虚函数地址:0X%p,->", i, VTable[i]);
		
		VFPtr f = VTable[i];
		f();    //调用方便看存的是那个函数
	}
	cout << endl;
}

注意:传参调用PrintVTable的思路

int main()
{
	Base b;
	Derive d;

	//思路:取b、d对象的头4个字节,就是虚表的指针。
	//以b对象讲解
	//1.先取b的地址,强转成int*的指针
	//2.再解引用取值,就取到b对象头4个字节的值,也就是指向虚表的指针
	//3.再强转成VFPtr*,因为虚表就是一个存VFPtr类型(虚函数指针类型)的数组
	//4.虚表指针传递给PrintVTable进行打印虚表

	VFPtr* vTable_b = (VFPtr*)(*(int*)&b);
	PrintVTable(vTable_b);

	VFPtr* vTable_d = (VFPtr*)(*(int*)&d);
	PrintVTable(vTable_d);

	return 0;
}

上面代码打印出虚表中虚函数的结果分析:
代码打印出虚表中虚函数的结果分析

2>. 多继承中的虚函数表

1. 多继承

test code:

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

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

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

typedef void(*VFPtr) ();  //函数指针
void PrintVTable(VFPtr vTable[])
{
	//依次取虚表中的虚函数指针打印并调用。
	cout << "虚表地址>" << vTable << endl;
	for (size_t i = 0; vTable[i] != nullptr; i++)
	{
		printf("第%d个虚函数地址:0X%p,->", i, vTable[i]);

		VFPtr f = vTable[i];
		f();    //调用方便看存的是那个函数
	}
	cout << endl;
}


int main()
{
	Derive d;

	VFPtr* vTable_b1 = (VFPtr*)(*(int*)&d);
	PrintVTable(vTable_b1);

	//(char*)&d  这里一定要注意强转,否则+1,就是加一个Derive的大小
	VFPtr* vTable_b2 = (VFPtr*)(*(int*)((char*)&d + sizeof(Base1)));
	PrintVTable(vTable_b2);
	return 0;
}

多继承测试代码展开分析:
多继承测试代码分析

2. 菱形继承

test code:

#include<iostream>
using namespace std;
class A
{
public:
	virtual void fun1()
	{
		cout << "A::fun1()" << endl;
	}
	int _a = 0;
};

class B : public A
{
public:
	virtual void fun1()
	{
		cout << "B::fun1()" << endl;
	}
	virtual void fun2()
	{
		cout << "B::fun2()" << endl;
	}
	int _b = 0;
};

class C : public A
{
public:
	virtual void  fun1()
	{
		cout << "C::fun1()" << endl;
	}
	virtual void fun2()
	{
		cout << "C::fun2()" << endl;
	}
	int _c = 0;
};
class D : public B, public C
{
public:
	virtual void  fun2()
	{
		cout << "D::fun2()" << endl;
	}
	virtual void fun3()
	{
		cout << "D::fun3()" << endl;
	}
};

typedef void(*VFPtr) ();  //函数指针
void PrintVTable(VFPtr vTable[])
{
	//依次取虚表中的虚函数指针打印并调用。
	cout << "虚表地址>" << vTable << endl;
	for (size_t i = 0; vTable[i] != nullptr; i++)
	{
		printf("第%d个虚函数地址:0X%p,->", i, vTable[i]);

		VFPtr f = vTable[i];
		f();    //调用方便看存的是那个函数
	}
	cout << endl;
}

int main()
{
	D d;
	VFPtr* vTable_d = (VFPtr*)(*(int*)&d);
	PrintVTable(vTable_d);
	
	C* ptr1 = &d;
	VFPtr* vTable_c = (VFPtr*)(*(int*)ptr1);
	PrintVTable(vTable_c);

	return 0;
}

菱形继承测试代码展开分析:(菱形继承和多继承没有什么大的区别)
菱形继承测试代码展开分析

3. 菱形虚拟继承

  1. 只有A类有虚函数

test code:

class A
{
public:
	virtual void fun1()
	{
		cout << "A::fun1()" << endl;
	}
public:
	int _a = 0;
};

class B : virtual public A
{
public:
	int _b = 0;
};

class C : virtual public A
{
public:
	int _c = 0;
};
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;
}

代码内存分析:
代码内存分析

  1. B和C完成对A的虚函数重写

test code:

class A
{
public:
	virtual void fun1()
	{
		cout << "A::fun1()" << endl;
	}
public:
	int _a = 0;
};

class B : virtual public A
{
public:
	virtual void fun1()
	{
		cout << "B::fun1()" << endl;
	}
public:
	int _b = 0;
};

class C : virtual public A
{
public:
	virtual void fun1()
	{
		cout << "C::fun1()" << endl;
	}
public:
	int _c = 0;
};
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;
}

运行结果:
编译报错

原因:因为整个对象只有A一张虚表是共享的,B要重写,C也要重写。不明确到底重写谁的。
解决办法:让D重写即可。当然B和C的重写并不是没有意义,如果定义B和C类型的对象,单独使用还是有意义的。

  1. 只有A类有虚函数 + B和C有单独的虚函数

test code:

class A
{
public:
	virtual void func1()
	{
		cout << "A::func1" << endl;
	}
public:
	int _a;
};

class B : virtual public A
{
public:
	virtual void func1()
	{
		cout << "B::func1" << endl;
	}

	virtual void func2()
	{
		cout << "B::func2" << endl;
	}
public:
	int _b;
};

class C : virtual public A
{
public:
	virtual void func1()
	{
		cout << "C::func1" << endl;
	}

	virtual void func2()
	{
		cout << "C::func2" << endl;
	}
public:
	int _c;
};

class D : public B, public C
{
public:
	virtual void func1()
	{
		cout << "D::func1" << endl;
	}

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

代码内存分析:
代码内存分析

  1. 如果在第三点的基础上,D类也有自己的虚函数,那么将放哪里?

test code:

class A
{
public:
	virtual void func1()
	{
		cout << "A::func1" << endl;
	}
public:
	int _a;
};

class B : virtual public A
{
public:
	virtual void func1()
	{
		cout << "B::func1" << endl;
	}

	virtual void func2()
	{
		cout << "B::func2" << endl;
	}
public:
	int _b;
};

class C : virtual public A
{
public:
	virtual void func1()
	{
		cout << "C::func1" << endl;
	}

	virtual void func2()
	{
		cout << "C::func2" << endl;
	}
public:
	int _c;
};

class D : public B, public C
{
public:
	virtual void func1()
	{
		cout << "D::func1" << endl;
	}

	virtual void func3()
	{
		cout << "D::func3" << endl;
	}

public:
	int _d;
};


typedef void(*VFPtr) ();  //函数指针
void PrintVTable(VFPtr vTable[])
{
	//依次取虚表中的虚函数指针打印并调用。
	cout << "虚表地址>" << vTable << endl;
	for (size_t i = 0; vTable[i] != nullptr; i++)
	{
		printf("第%d个虚函数地址:0X%p,->", i, vTable[i]);

		VFPtr f = vTable[i];
		f();    //调用方便看存的是那个函数
	}
	cout << endl;
}


int main()
{
	D d;
	VFPtr* vTable_d = (VFPtr*)(*(int*)&d);    //B的虚表
	PrintVTable(vTable_d);
		
	C* ptr1 = &d;
	VFPtr* vTable_c = (VFPtr*)(*(int*)ptr1);  //C的虚表
	PrintVTable(vTable_c);
	
	A* ptr2 = &d;
	VFPtr* vTable_a = (VFPtr*)(*(int*)ptr2);  //A的虚表
	PrintVTable(vTable_a);

	return 0;
}

运行结果:由结果得到D类自己的虚函数放在第一张虚表中
代码运行结果

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

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

相关文章

JavaScript函数调用其他函数

在JavaScript中&#xff0c;函数可以调用其他函数。这通常被称为函数组合&#xff0c;它允许你通过将较简单的函数组合在一起来创建更复杂的功能。 例如&#xff1a;还是以之前的水果加工举例&#xff0c;但是现在我们需要输出&#xff0c;这个苹果有几块&#xff0c;橘子有几块…

微信小程序分享后真机参数获取不到和部分参数不能获取问题问题解决

微信小程序的很多API&#xff0c;都是BUG&#xff0c;近期开发小程序就遇到了分享后开发工具可以获取参数&#xff0c;但是真机怎么都拿不到参数的问题 一、真机参数获取不到问题解决 解决方式&#xff1a; 在onLoad(options) 中。 onLoad方法中一定要有options 这个参数。…

【论文解读】基于图的自监督学习联合嵌入预测架构

一、简要介绍 本文演示了一种学习高度语义的图像表示的方法&#xff0c;而不依赖于手工制作的数据增强。论文介绍了基于图像的联合嵌入预测架构&#xff08;I-JEPA&#xff09;&#xff0c;这是一种用于从图像中进行自监督学习的非生成性方法。I-JEPA背后的idea很简单&#xff…

博客写作神器:介绍与部署Markdown Nice

作为开发者和写作者&#xff0c;我们经常需要使用Markdown来编写文档、博客和技术文稿。然而&#xff0c;标准的Markdown格式可能显得有些普通&#xff0c;缺乏一些专业的排版效果。在这里&#xff0c;我们将向你介绍一个强大的工具——Markdown Nice&#xff08;mdnice&#x…

Android中的APK打包与安全

aapt2命令行实现apk打包 apk文件结构 classes.dex&#xff1a;Dex&#xff0c;即Android Dalvik执行文件 AndroidManifest.xml&#xff1a;工程中AndroidManifest.xml编译后得到的二进制xml文件 META-INF&#xff1a;主要保存各个资源文件的SHA1 hash值&#xff0c;用于校验…

开始MySQL之路——MySQL安装和卸载

MySQL的介绍 MySQL数据库管理系统由瑞典的DataKonsultAB公司研发&#xff0c;该公司被Sun公司收购&#xff0c;现在Sun公司又被Oracle公司收购&#xff0c;因此MySQL目前属于Oracle旗下产品。 MySQL所使用的SQL语言是用于访问数据库的最常用标准化语言。MySQL软件采用了双授权…

使用VSCode SSH实现公网远程连接本地服务器开发的详细教程

文章目录 前言1、安装OpenSSH2、vscode配置ssh3. 局域网测试连接远程服务器4. 公网远程连接4.1 ubuntu安装cpolar内网穿透4.2 创建隧道映射4.3 测试公网远程连接 5. 配置固定TCP端口地址5.1 保留一个固定TCP端口地址5.2 配置固定TCP端口地址5.3 测试固定公网地址远程 前言 远程…

什么是Sui Kiosk,它可以做什么,如何赋能创作者?

创作者和IP持有者需要一些工具帮助他们在区块链上实现其商业模式。Sui Kiosk作为Sui上的一种原语可以满足这种需求&#xff0c;为创作者提供动态选项&#xff0c;使他们能够在任何交易场景中设置完成交易的条件。 本文将向您介绍为什么要在SuiFrens中使用Sui Kiosk&#xff0c…

数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。

文章目录 前言一、单源最短路径1、单源最短路径问题2、Dijkstra 初始化a、参数b、初始化参数c、算法步骤 3、Dijkstra 算法详细步骤a、第一轮算法执行b、第二轮算法执行c、第三轮算法执行d、第四轮算法执行e、第五轮算法执行f、第六轮算法执行 4、java算法实现 二、多源最短路径…

怎么把pdf转换成jpg格式?

怎么把pdf转换成jpg格式&#xff1f;在我们日常的办公过程中&#xff0c;PDF文件是一个经常被使用来传输文件的格式。它能够确保我们的文件内容不会混乱&#xff0c;并以更加完美的方式呈现出来。然而&#xff0c;PDF文件也存在一些缺陷。例如&#xff0c;它无法直接编辑&#…

Win10下CCS v5.2.1编译错误Fatal error: could not open source file问题记录

Win10下CCS v5.2.1编译错误Fatal error: could not open source file问题记录 1.问题现象 作者在Win 10系统中使用CCS v5.2.1进行DSP C6678开发&#xff0c;由于更换了新的电脑&#xff0c;所以重新配置了开发环境&#xff0c;但是编译出现错误。输出如下&#xff1a; **** …

网关认证的技术方案

我们认证授权使用springsecurity 和oauth2技术尽心实现具体实现流程见第五章文档&#xff0c;这里就是记录一下我们的技术方案 这是最开始的技术方案&#xff0c;我们通过认证为服务获取令牌然后使用令牌访问微服务&#xff0c;微服务解析令牌即可。但是缺点就是每个微服务都要…

Hystrix: 服务降级

cloud是基础&#xff0c;eureka是服务注册和发现&#xff0c;consumer是消费者去消费provider里的东西&#xff0c;消费方式就是Feign和Ribbon&#xff0c;feign 接口消费&#xff0c;ribbon Rest消费 服务降级发生在客户端&#xff0c;客户端因为请求关闭的服务器&#xff0…

Django基础4——模板系统

文章目录 一、基本了解1.1 引用变量1.2 全局变量 二、if判断2.1 语法2.2 案例 三、for循环3.1 语法3.2 案例3.3 forloop变量3.4 容错语句 四、过滤器4.1 内置过滤器4.2 自定义过滤器 五、模板继承六、模板导入七、引用静态文件 一、基本了解 概念&#xff1a; Django模板系统&a…

AI 时代,程序员无需焦虑 | 《服务端开发:技术、方法与实用解决方案》(文末送书福利4.0)

文章目录 &#x1f4cb;前言&#x1f3af;程序员会被 AI 取代么&#xff1f;&#x1f3af;服务端开发尚难被 AI 取代&#x1f3af; 服务端开发何去何从&#xff1f;&#x1f3af;业界首部体系化、全景式解读服务端开发的著作&#x1f4ac;读者对象&#x1f4da;本书优势&#x…

Shell 编程快速入门 之 函数基础知识

目录 shell函数基础知识 函数定义 函数名 函数体 参数 返回值 return返回值的含义 return与echo返回值的区别 可变参数函数 自定义库函数 定义库函数 调用库函数 执行结果 递归函数 阶乘函数 斐波那契函数 shell函数基础知识 函数定义 函数名 Shell函数用…

深度学习优化入门:Momentum、RMSProp 和 Adam

目录 深度学习优化入门&#xff1a;Momentum、RMSProp 和 Adam 病态曲率 1牛顿法 2 Momentum:动量 3Adam 深度学习优化入门&#xff1a;Momentum、RMSProp 和 Adam 本文&#xff0c;我们讨论一个困扰神经网络训练的问题&#xff0c;病态曲率。 虽然局部极小值和鞍点会阻碍…

LLM-chatgpt训练过程

流程简介 主要包含模型预训练和指令微调两个阶段 模型预训练&#xff1a;搜集海量的文本数据&#xff0c;无监督的训练自回归decoder&#xff1b; O T P ( O t < T ) O_TP(O_{t<T}) OT​P(Ot<T​)&#xff0c;损失函数CE loss指令微调&#xff1a;在输入文本中加入…

注解和class对象和mysql

注解 override 通常是用在方法上的注解表示该方法是有重写的 interface 表示一个注解类 比如 public interface override{} 这就表示是override是一个注解类 target 修饰注解的注解表示元注解 deprecated 修饰某个元素表示该元素已经过时了 1.不代表该元素不能用了&…

开源项目的社区建设与管理

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…