【golang】如何定制化zap日志库以及如何使用
Zap 日志
前言
本文主要介绍Go语言日志库如何简易定制化,以及如何在开发中使用。
为什么需要日志?
一个产品的诞生一定是因为有需求!新技术大部分都是为了更加便利和实用而诞生的,日志也不例外。日志顾名思义就是对整个项目的事件进行记录。日志可以帮助我们查看某一天中某一时刻项目的运转情况等等。
日志的好处
在日常开发过程中难免会遇到BUG出现的情况,日志可以记录这些BUG出现的地点从而方便进行快速定位和排查。可以根据需求对日志进行自定义的输出,比如输出到控制台、文件等。日志也可以帮助我们在开发过程中检测到程序潜在的问题和程序运行的流程,能够有效的提高我们的开发效率。
日志都有什么
要让程序记录有效的,便利的日志。** Logger (日志记录器) 应该具备以下特点**:
-
可以将日志信息输出到控制台、文件等地方,输出到文件便于项目长久运行,输出到控制台有助于开发过程中检错的效率。
-
一个日志应该具有多个基本的级别,比如
info
,debug
,warn
,error
,fatal
等,他们可以对日志进行分类。 -
可以对日志进行切割,按照日志大小、日期、时间间隔等因素分割。
-
可以手动或自动记录一些开发信息。如前端传入的数据,异常错误信息,程序运行结果,错误行数,日志打印位置等等信息进行打印。
Go中默认的日志
Go语言中默认集成了一个log
日志库
func New(out io.Writer, prefix string, flag int) *Logger {l := &Logger{out: out, prefix: prefix, flag: flag}if out == io.Discard {l.isDiscard = 1}return l
}
使用New
可以获取到该日志对象。第一个参数为实现了Writer
接口的对象。可以使用os.OpenFile()
选择一个文件,然后将该文件对象作为输出,也可以使用os.Stdout
或os.Stderr
输出到控制台。第二个参数需要传入一个日志信息每一行的前缀(如果输出到控制台该处可以填空字符串)。第三个参数是设置打印默认信息的能力,比如打印时间等。
测试日志
var l *log.Loggerfunc main() {l.Printf("main method exec fail, err: %v", errors.New("nil Pointer error"))l.Println("test go log status")l.Fatal("wait five seconds")time.Sleep(time.Second * 5)l.Println("five seconds after!")
}func init() {l = log.New(os.Stdout, "[我是一个前缀]", log.LstdFlags)
}
打印信息:
[我是一个前缀]2023/02/10 21:15:22 main method exec fail, err: nil Pointer error
[我是一个前缀]2023/02/10 21:15:22 test go log status
[我是一个前缀]2023/02/10 21:15:22 wait five seconds
// Fatal is equivalent to l.Print() followed by a call to os.Exit(1).
func (l *Logger) Fatal(v ...any) {l.Output(2, fmt.Sprint(v...))os.Exit(1)
}
在Fatal
之后的程序均不会执行,因为Fatal
执行后会在内部调用os.Exit(1)
,从而在打印结束后退出进程。
goLogger的不足
- 日志级别只支持
Fatal
,只有一个Print
函数,没有其他级别。 - 日志自定义参数过少,无法打印栈信息,无法确定请求位置等。
Fatal
和Painc
都是执行后退出,无法容忍错误情况的出现就会退出程序。- 无法指定输出格式,只能以文本形式进行输出,没有根据日志大小、时间间隔、日期进行分割的能力。
虽然gologger
支持并发,但也只限于简单用着还行,实际开发用起来并不舒服的情况。
Zap日志库
引入日志库依赖
go get -u go.uber.org/zap
zap
日志库是Uber
开源的。性能很好,因为不用反射实现,但需要自己去手动指明打印信息的类型(下面会有示例)。个人觉得自己指定打印还是挺舒服的。zap
的使用率非常高,不仅支持日志库的基本功能,而且很灵活的支持你去进一步的封装或者定制化。zap
支持异步打印。
如何使用zap
格式化配置
func NewDevelopmentEncoderConfig() zapcore.EncoderConfig
func NewProductionEncoderConfig() zapcore.EncoderConfig
func NewProductionConfig() Config
func NewDevelopmentConfig() Config
这里可以根据实际生产和测试环境需求进行选择,也可以直接使用其他初始化方式。
// NewProductionEncoderConfig returns an opinionated EncoderConfig for
// production environments.
func NewProductionEncoderConfig() zapcore.EncoderConfig {return zapcore.EncoderConfig{// 设置log内容里的一些属性的keyTimeKey: "ts",//时间对应的key名LevelKey: "level",//日志级别对应的key名NameKey: "logger",//logger名对应的key名CallerKey: "caller",//调用者对应的key名FunctionKey: zapcore.OmitKey,MessageKey: "msg",//日志内容对应的key名,此参数必须不为空,否则日志主体不处理StacktraceKey: "stacktrace",//栈追踪的key名// const DefaultLineEnding = "\n" 行末输出格式LineEnding: zapcore.DefaultLineEnding,// 日志编码级别EncodeLevel: zapcore.LowercaseLevelEncoder,// 日志时间解析EncodeTime: zapcore.EpochTimeEncoder,// 日志日期解析EncodeDuration: zapcore.SecondsDurationEncoder,// 日志调用路径EncodeCaller: zapcore.ShortCallerEncoder,}
}
使用NewProductionEncoderConfig()
创建的 Logger
在记录日志时会自动记录调用函数的信息、打日志的时间,日志级别等信息。
EncodeLevel
// A LevelEncoder serializes a Level to a primitive type.
type LevelEncoder func(Level, PrimitiveArrayEncoder)// 将日志级别进行大写并带上颜色
func CapitalColorLevelEncoder(l Level, enc PrimitiveArrayEncoder)
// 将日志级别大写不带颜色
func CapitalLevelEncoder(l Level, enc PrimitiveArrayEncoder)
// 将日志级别小写带上颜色
func LowercaseColorLevelEncoder(l Level, enc PrimitiveArrayEncoder)
// 将日志级别小写不带颜色
func LowercaseLevelEncoder(l Level, enc PrimitiveArrayEncoder)
需要实现LevelEncoder
接口。可以调整日志编码级别,并且选择带上或者不带输出颜色。
EncodeTime
// A TimeEncoder serializes a time.Time to a primitive type.
type TimeEncoder func(time.Time, PrimitiveArrayEncoder)// 根据不同时间进行格式化
func EpochTimeEncoder(t time.Time, enc PrimitiveArrayEncoder)
func EpochMillisTimeEncoder(t time.Time, enc PrimitiveArrayEncoder)
func EpochNanosTimeEncoder(t time.Time, enc PrimitiveArrayEncoder)
定制化时间格式解析,需要实现TimeEncoder
接口。
EncodeDruation
// A DurationEncoder serializes a time.Duration to a primitive type.
type DurationEncoder func(time.Duration, PrimitiveArrayEncoder)// 将日期根据不同时间进行格式化
func SecondsDurationEncoder(d time.Duration, enc PrimitiveArrayEncoder)
func NanosDurationEncoder(d time.Duration, enc PrimitiveArrayEncoder)
func MillisDurationEncoder(d time.Duration, enc PrimitiveArrayEncoder)
func StringDurationEncoder(d time.Duration, enc PrimitiveArrayEncoder)
定制日期格式解析。需要实现DruationEncoder
接口
定制化zap
编码格式
encoderConfig := zap.NewProductionEncoderConfig()
// 打印级别为大写 & 彩色
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
// 时间编码进行指定格式解析 layout -> "[2006-01-02 15:04:05]"
encoderConfig.EncodeTime = parseTime(settings.Conf.Layout)
修改日志打印级别和时间编码格式
// parseTime 进行时间格式处理
func parseTime(layout string) zapcore.TimeEncoder {return func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {type appendTimeEncoder interface {AppendTimeLayout(time.Time, string)}if enc, ok := enc.(appendTimeEncoder); ok {enc.AppendTimeLayout(t, layout)return}enc.AppendString(t.Format(layout))}
}
实现zapcore.TimeEncoder
接口,将指定的Layout
参数进行传入实现闭包即可。
日志分割
// 日志输出配置, 借助另外一个库 lumberjack 协助完成日志切割。
lumberjackLogger := &lumberjack.Logger{Filename: settings.Conf.Filename, // -- 日志文件名MaxSize: settings.Conf.MaxSize, // -- 最大日志数 M为单位!!!MaxAge: settings.Conf.MaxAge, // -- 最大存在天数MaxBackups: settings.Conf.MaxBackups, // -- 最大备份数量Compress: false, // --是否压缩
}
syncer := zapcore.AddSync(lumberjackLogger)
zap
日志本身不支持日志切割,借助另外一个库 lumberjack
协助完成日志切割。
// -- 用于开发者模式和生产模式之间的切换
var core zapcore.Core
if settings.Conf.AppConfig.Mode == "debug" {encoder := zapcore.NewConsoleEncoder(encoderConfig) // 输出控制台编码格式core = zapcore.NewTee(zapcore.NewCore(encoder, syncer, zapcore.DebugLevel), // debug级别打印到日志文件zapcore.NewCore(encoder, zapcore.Lock(os.Stdout), zapcore.DebugLevel), // debug级别打印到控制台)
} else {encoder := zapcore.NewJSONEncoder(encoderConfig)// 输出Json格式,便于日志检索core = zapcore.NewCore(encoder, syncer, zapcore.InfoLevel)// info级别打印到日志文件
}
lg := zap.New(core, zap.AddCaller()) // --添加函数调用信息
根据配置信息去选择具体打印需求。
zap.ReplaceGlobals(lg) // 替换该日志为全局日志var (_globalMu sync.RWMutex_globalL = NewNop()
)// L returns the global Logger, which can be reconfigured with ReplaceGlobals.
// It's safe for concurrent use.
func L() *Logger {_globalMu.RLock()l := _globalL_globalMu.RUnlock()return l
}
设置该日志为全局日志,将原日志进行替换,即可在任意位置使用zap.L()
调用该日志。
完整代码
// init 初始化日志库
func init() {encoderConfig := zap.NewProductionEncoderConfig()// 打印级别为大写 & 彩色encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder// 时间编码进行指定格式解析encoderConfig.EncodeTime = parseTime(settings.Conf.Layout)// 日志输出配置, 借助另外一个库 lumberjack 协助完成日志切割。lumberjackLogger := &lumberjack.Logger{Filename: settings.Conf.Filename, // -- 日志文件名MaxSize: settings.Conf.MaxSize, // -- 最大日志数 M为单位!!!MaxAge: settings.Conf.MaxAge, // -- 最大存在天数MaxBackups: settings.Conf.MaxBackups, // -- 最大备份数量Compress: false, // --是否压缩}syncer := zapcore.AddSync(lumberjackLogger)// -- 用于开发者模式和生产模式之间的切换var core zapcore.Coreif settings.Conf.AppConfig.Mode == "debug" {encoder := zapcore.NewConsoleEncoder(encoderConfig)core = zapcore.NewTee(zapcore.NewCore(encoder, syncer, zapcore.DebugLevel),zapcore.NewCore(encoder, zapcore.Lock(os.Stdout), zapcore.DebugLevel),)} else {encoder := zapcore.NewJSONEncoder(encoderConfig)core = zapcore.NewCore(encoder, syncer, zapcore.InfoLevel)}lg := zap.New(core, zap.AddCaller()) // --添加函数调用信息zap.ReplaceGlobals(lg) // 替换该日志为全局日志
}// parseTime 进行时间格式处理
func parseTime(layout string) zapcore.TimeEncoder {return func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {type appendTimeEncoder interface {AppendTimeLayout(time.Time, string)}if enc, ok := enc.(appendTimeEncoder); ok {enc.AppendTimeLayout(t, layout)return}enc.AppendString(t.Format(layout))}
}
测试日志打印情况
zap.L().Info("test info", zap.String("test String", "ok"), zap.Int("test cnt", 1))
zap.L().Debug("test debug", zap.String("test String", "ok"), zap.Int("test cnt", 2))
zap.L().Error("test error", zap.String("test String", "ok"), zap.Int("test cnt", 3))
[2023-02-10 22:22:17] INFO xxxxx/main.go:22 test info {“test String”: “ok”, “test cnt”: 1}
[2023-02-10 22:22:17] DEBUG xxxxx/main.go:23 test debug {“test String”: “ok”, “test cnt”: 2}
[2023-02-10 22:22:17] ERROR xxxxx/main.go:24 test error {“test String”: “ok”, “test cnt”: 3}
这里就是上述所说的自指定类型进行输出的情况。
结合gin框架进行使用
虽然gin
框架有自带的logger
中间件,但我们可以根据gin
框架实现的原生日志和异常恢复中间件进行改造并进行替换。
Loger
// GinLogger 替换gin中默认的logger
func GinLogger() gin.HandlerFunc {return func(c *gin.Context) {start := time.Now()path := c.Request.URL.Pathquery := c.Request.URL.RawQueryc.Next()cost := time.Since(start)if c.Writer.Status() != http.StatusOK {// 记录异常信息zap.L().Error(query,zap.Int("status", c.Writer.Status()),zap.String("method", c.Request.Method),zap.String("path", path),zap.String("ip", c.ClientIP()),zap.String("user-agent", c.Request.UserAgent()),zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),zap.Duration("cost", cost),)}
}
如果有错误请求,只要不是状态码为200的全部进行打印->状态码、请求方法(get、post…)、路径、ip、用户授权方、错误信息、请求花费时间。
// GinRecovery recover掉项目可能出现的panic
func GinRecovery(stack bool) gin.HandlerFunc {return func(c *gin.Context) {defer func() {if err := recover(); err != nil {// Check for a broken connection, as it is not really a// condition that warrants a panic stack trace.var brokenPipe boolif ne, ok := err.(*net.OpError); ok {if se, ok := ne.Err.(*os.SyscallError); ok {if strings.Contains(strings.ToLower(se.Error()), "broken pipe") ||strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {brokenPipe = true}}}httpRequest, _ := httputil.DumpRequest(c.Request, false)if brokenPipe {zap.L().Error(c.Request.URL.Path,zap.Any("error", err),zap.String("httpRequest", string(httpRequest)),)// If the connection is dead, we can't write a status to it.c.Error(err.(error)) // nolint: errcheckc.Abort()return}// 这里可以选择全部打印出来不必要分割然后循环输出request := strings.Split(string(httpRequest), "\r\n")split := strings.Split(string(debug.Stack()), "\n\t")if stack {zap.L().Error("[Recovery from panic]",zap.Any("error", err))for _, str := range request {zap.L().Error("[Recovery from request panic]", zap.String("request", str))}for _, str := range split {zap.L().Error("[Recovery from Stack panic]", zap.String("stack", str))}} else {zap.L().Error("[Recovery from panic]",zap.Any("error", err))for _, str := range request {zap.L().Error("[Recovery from request panic]", zap.String("request", str))}}c.AbortWithStatus(http.StatusInternalServerError)}}()c.Next()}
}
这里在Panic
的时候我采用了分割循环打印的方法,也可以全部输出,但是一堆异常情况,不容易看清楚。也可以选择不打印栈轨迹输出,只需要在使用recover
中间件时传入false
参数即可。
小结
zap日志可以灵活的定制时间、编码输出格式、颜色等信息。
zap日志级别丰富,不利用反射,效率高,但需要手动对类型进行定义。
相关文章:

【golang】如何定制化zap日志库以及如何使用
Zap 日志 前言 本文主要介绍Go语言日志库如何简易定制化,以及如何在开发中使用。 为什么需要日志? 一个产品的诞生一定是因为有需求!新技术大部分都是为了更加便利和实用而诞生的,日志也不例外。日志顾名思义就是对整个项目的事件进行记…...

如何将 Ubuntu 升级到 22.04 LTS Jammy Jellyfish
在本教程中,我们将详细介绍如何将你的 Ubuntu 系统升级到版本 22.04 Jammy Jellyfish,这是最新的长期支持版本。 Ubuntu 22.04 LTS Jammy Jellyfish 将于 2022 年 4 月 21 日发布。它是下个两年一次的长期支持(LTS)版本,因此值得注意,而且现在 Ubuntu 21.10 的用户可以升…...

ubuntu20.04安装docker与docker-compose
安装docker 查看系统发行版本 cat /proc/version1、更新apt包 sudo apt-get update2、安装必备的软件包以允许apt通过 HTTPS 使用存储库(repository): sudo apt-get install ca-certificates curl gnupg lsb-release3、添加Docker官方版本…...

笔试题-2023-加特兰-数字IC设计【纯净题目版】
回到首页:2023 数字IC设计秋招复盘——数十家公司笔试题、面试实录 推荐内容:数字IC设计学习比较实用的资料推荐 题目背景 笔试时间:2022.07.27应聘岗位:数字电路设计工程师(SoC) - 2023届笔试时长:90min笔试平台:nowcoder牛客网题目类型:问答题(11道)主观评价 难易…...

动态内存管理
目录1.为什么要动态内存分配2.动态内存函数malloc](https://cplusplus.com/reference/cstdlib/malloc/?kwmalloc)和[freecallocrealloc3.使用动态内存要注意的几点对NULL的解引用对同一块动态内存多次释放free非动态开辟的内存使用free释放一块动态开辟内存的一部分一个函数中…...

Unsupervised Question Answering 简单综述
Unsupervised Question Answering by Cloze Translation, ACL 2019 随机从文本中抽取noun phrases或者named entity作为答案将答案部分mask掉,生成cloze question利用无监督翻译,将cloze question转化为natural question 缺点: 直接利用原句…...

智慧物流管理系统
智慧物流运用物联网、大数据、云计算、人工智能等技术优化物流决策过程。智慧物流获取、分析物流信息并做出决策,从商品源开始实时跟踪与管理,保证信息流快于商品流,实现信息与物质快速、高效、流畅地运转,集自动化、数字化、网络…...

单表查询--实例
#素材: 表名:worker-- 表中字段均为中文,比如 部门号 工资 职工号 参加工作 等 >CREATE TABLE worker ( >部门号 int(11) NOT NULL, >职工号 int(11) NOT NULL, >工作时间 date NOT NULL, >工资 float(8,2) NOT NULL, >政治…...

c语言递归 累和 ,累乘积,斐波那契数列,字符串长度
目录 递归使用场景 1:使用递归的方式计算 Sn123..100 2:计算 n!n*(n-1)*(n-2)*......*1; 3:计算输出斐波那契数列前20项,并按每行4个数的格式输出(2019年) 4: 用递归和非递归两种方式编写函数strlength()。该函数…...

数据与C(ASCII码,char)
目录 一.ASCII码讲解 二.非打印字符(转义字符) 三.扩展小知识 一.ASCII码讲解 char类型用于存储字符,从技术层面看,char时整数类型,因为char类型实际上存储的是整数而不是字符。计算机使用数字编码来处理字符&…...

第一个C语言代码(visual studin创建调试以及项目文件功能讲解)
这里我主要使用visual Studio进行编程 目录 一.创建项目 二.编写代码 1.代码编写 2.代码分析 3.main() 4.注释符 5.{} 花括号 6.声明 7.赋值 8.printf()函数 9.return 0; 一.创建项目 这里大家可能会比较疑惑,为啥都是C,没看见C的项目&…...

VIF原理
文章目录一、VIF公式和原理对于R方一般回归模型皮尔逊相关系数中的方差VIF原理:一、VIF公式和原理 所谓VIF方法,计算难度并不高。在线性回归方法里,应用最广泛的就是最小二乘法(OLS),只不过我们对每个因子…...

nginx相关反爬策略总结笔记
引言 互联网站点的流量一部分由人类正常访问行为产生,而高达30%-60%的流量则是由网络爬虫产生的,其中一部分包含友好网络爬虫,如搜索引擎的爬虫、广告程序、第三方合作伙伴程序、Robots协议友好程序等;而并非所有的网络爬虫都是友好的&#x…...

【Vue3】电商网站吸顶功能
头部分类导航-吸顶功能 电商网站的首页内容会比较多,页面比较长,为了能让用户在滚动浏览内容的过程中都能够快速的切换到其它分类。需要分类导航一直可见,所以需要一个吸顶导航的效果。 目标:完成头部组件吸顶效果的实现 交互要求 滚动距离大…...

HOMER docker版本安装详细流程
概述 HOMER是一款100%开源的针对SIP/VOIP/RTC的抓包工具和监控工具。 HOMER是一款强大的、运营商级、可扩展的数据包和事件捕获系统,是基于HEP/EEP协议的VoIP/RTC监控应用程序,并可以使用即时搜索、处理和存储大量的信令、RTC事件、日志和统计信息。 …...

【数据结构】单向链表的练习题
目录 前言 1、删除链表中等于给定值val的所有节点。 【题目描述】 【代码示例】 【 画图理解】 2、反转一个点链表 【题目描述】 【 代码思路】 【代码示例】 【画图理解】 3、给定一个带有头节点head的非空单链表,返回链表的中间节点,如果有两个…...

我的企业需要一个网站吗?答案是肯定的 10 个理由
如果您的企业在没有网站的情况下走到了这一步,您可能会想:我的企业需要一个网站吗?如果我的企业没有一个就已经成功了,那又有什么意义呢?简短的回答是,现在是为您的企业投资网站的最佳或更重要的时机。网站…...

CHI协议定义的NOC组件
请求结点RN 可以向NOC发送读/写等请求事务,有以下几种类型的RN: RN-F 一般是处理器核或者核簇结点,包含了局部cache和一致性部件snoopee。与NOC上的一致性部件一起,维护“可缓存”数据的一致性(这种可缓存数据…...

Python+Flask+MySQL开发的在线外卖订餐系统(附源码)
文章目录一、项目模块及功能介绍1、登录模块2、注册模块3、商家用户模块4、买家用户模块5、系统管理员模块源码二、项目结构三、环境依赖四、运行方法五、系统部分界面展示1、首页2、注册界面3、登录界面4、商家主界面5、商家菜单界面6、商家添加菜品界面7、商家修改菜品界面8、…...

OpenStack云平台搭建(4) | 部署Placement
目录 安装部署Placement 1、登录数据库授权 2、安装palcement-api 安装部署Placement 【Placement】服务 是从【nova】服务中拆分出来的组件,作用是收集各个【node】节点的可用资源,把【node】节点的资源统计写入到【MySQL】【Placement】服务会被【n…...

GNN图神经网络原理解析
一、GNN基本概念 1. 图的基本组成 图神经网络的核心就是进行图模型搭建,图是由点和边组成的。在计算机处理时,通常将数据以向量的形式进行存储。因此,在存储图时,就会有点的向量,点与点之间边的向量,全局向量(描述整张图),邻接矩阵(记录哪些点之间存在关联)等。 既…...

BI-SQL丨ALL、ANY、SOME
ALL、ANY、SOME ALL、ANY和SOME,这三个关键字,在SQL中使用频率较高,通常可以用来进行数据比较筛选。 注:SQL中ALL的用法和DAX中ALL的用法是完全不同的,小伙伴不要混淆了。 那么三者之间的区别是什么呢? A…...

从0到0.1学习 maven(三:声明周期、插件、聚合与继承)
该文章为maven系列学习的第三篇,也是最后一篇 第一篇快速入口:从0到0.1学习 maven(一:概述及简单入门) 第二篇快速入口:从0到0.1学习 maven(二:坐标、依赖和仓库) 文章目录啥子叫生命周期生命周期详解clean生命周期def…...

【直击招聘C++】2.5 this指针
2.5 this指针一、要点归纳1.什么是this指针2.this指针的深入讨论程序1程序23.类成员函数返回对象和返回对象引用的区别二、面试真题解析面试题1面试题2一、要点归纳 1.什么是this指针 this指针是隐含于每一个类对象的特殊指针,该指针值是一个正在被某个成员函数操作…...

spark数据清洗练习
文章目录准备工作删除缺失值 > 3 的数据删除星级、评论数、评分中任意字段为空的数据删除非法数据hotel_data.csv通过编写Spark程序清洗酒店数据里的缺失数据、非法数据、重复数据准备工作 搭建 hadoop 伪分布或 hadoop 完全分布上传 hotal_data.csv 文件到 hadoopidea 配置…...

Android 12首次开机启动Launcher前黑屏问题解析
在工作中,对于系统开发确实有些难度,特别是在开机阶段遇到的问题,比如开机动画播放完毕进入锁屏界面黑屏几秒然后进入 锁屏界面,这就需要根据开机日志来分析问题所在,在工作中遇到的几种黑屏情况做下记录首次开机进入L…...

使用 LSSVM 的 Matlab 演示求解反常微分方程问题(Matlab代码实现)
目录 💥1 概述 📚2 运行结果 🎉3 参考文献 👨💻4 Matlab代码 💥1 概述 LSSVM的特性 1) 同样是对原始对偶问题进行求解,但是通过求解一个线性方程组(优化目标中的线性约束导致…...

动态规划-背包问题
文章目录一、背包问题1. 背包问题简介2. 背包问题解决方法二、01 背包问题1. 实现思路2. 实现代码三、完全背包问题1. 实现思路2. 实现代码四、多重背包问题(一)1. 实现思路2. 实现代码五、多重背包问题(二)1. 实现思路2. 实现代码…...

计算24点与运算符重载
十几年前写过一个算24点的程序。记得当时有点费劲,不过最后总算捣鼓出来了。前几天突然想再写一次,结果轻松地写出来了。C,总行数不多,带命令行界面和注释共200行不到;利用了面向对象和运算符重载来简化代码。 首先谈…...

MES系统智能工厂,搭上中国制造2025顺风车
MES在电子制造业中的应用日益广泛,越来越多的厂商已经购置或自行开发了MES,并将其作为“智能化工厂”。国内大大小小、各行各业都有上百个MES系统,还有很多的国外MES系统,怎么才能在MES系统公司中找到适合自己的MES?希…...