标准日志插件项目【C/C++】
博客主页:花果山~程序猿-CSDN博客
文章分栏:项目日记_花果山~程序猿的博客-CSDN博客
关注我一起学习,一起进步,一起探索编程的无限可能吧!让我们一起努力,一起成长!
目录
一,项目介绍
日志插件技术实现
同步日志
异步日志
相关技术补充
C不定参宏
C不定参函数
C++不定参函数
设计模式
工厂模式
建造者模式
代理模式
项目模块设计
日志框架设计
日志格式模块(format)
日志落地模块(sink)
日志管理器模块(logger)
扩展
双缓冲区异步任务处理器(Asynlogger)
异步日志器模块
日志宏
性能测试
模块关系图
改bug心得
结语
嗨!收到一张超美的图,愿你每天都能顺心!
一,项目介绍
- 企业开发中对于运行中的程序不适合使用调试器调试。⽣产环境的产品为了保证其稳定性及安全性是不允许开发⼈员附加调试器去排查问题, 可以借助⽇志系统来打印⼀些⽇志帮助开发⼈员解决问题。
- 问题无法复现。上线客⼾端的产品出现bug⽆法复现并解决, 可以借助⽇志系统打印⽇志并上传到服务端帮助开发⼈员进⾏分析。
- 对于⼀些⾼频操作(如定时器、⼼跳包)在少量调试次数下可能⽆法触发我们想要的⾏为,通过断点的暂停⽅式,我们不得不重复操作⼏⼗次、上百次甚⾄更多,导致排查问题效率是⾮常低下, 可 以借助打印⽇志的⽅式查问题 。
- 在分布式、多线程/多进程代码中, 出现bug⽐较难以定位, 可以借助⽇志系统打印log帮助定位bug。
本项⽬主要实现⼀个⽇志系统, 其主要⽀持以下功能:
- 支持日志等级选择
- 支持用户(开发人员)日志格式自定义
- 支持日志可靠落地,方向如:控制台,文件,滚动文件等
- 支持同步,异步模式选择
- 支持多线程程序并发写日志
核心技术:
- 类层次设计(继承 & 多态)
- C++(如:多线程,智能指针等)
- 双缓冲区
- 生产消费者模型
- 设计模式(单例,工厂,建造者,代理模式)
日志插件技术实现
现在我们日志输出方式主要是3种方式:
- printf,cout,等输出函数到控制台
- 对于⼤型商业化项⽬, 为了⽅便排查问题,我们⼀般会将⽇志输出到⽂件或者是数据库系统⽅便查询和分析⽇志, 主要分为同步⽇志和异步⽇志⽅式
同步日志
同步⽇志是指当输出⽇志时,必须等待⽇志输出语句执⾏完毕后,才能执⾏后⾯的业务逻辑语句,⽇志输出语句与程序的业务逻辑语句将在同⼀个线程运⾏。每次调⽤⼀次打印⽇志API就对应⼀次系统调⽤write写⽇志⽂件异步日志。
缺点:
- 在高并发情况下,由于写日志IO的系统操作,效率过低;
- 同时write操作有可能让线程阻塞,无法执行业务逻辑。
异步日志
异步日志就是将日志输出操作分离出,让一个线程负责日志输出操作,而业务线程只负责在写。业务线程只需要在内存中向日志缓冲区写入日志(日志的生产者),而日志线程就只负责从日志缓冲区中获取日志,完成日志的输出操作(日志的消费者)。这之间的关系就是一个典型的生产-消费者模型。
相关技术补充
C不定参宏
#include <stdio.h>// 定义一个不定参宏,用于打印消息
#define LOG(format, ...) printf(format, __VA_ARGS__)// 项目中通过它来简化接口调用
#define DEBUG(fmt, ...) \RoolLogger()->Debug(__FILE__, __LINE__, fmt, ##__VA_ARGS__)int main() {// 使用不定参宏打印不同数量的参数LOG("Hello, world!\n");LOG("The value of x is %d\n", 42);LOG("x = %d, y = %d, z = %d\n", 10, 20, 30);return 0;
}
C不定参函数
void Debug(const std::string &fmt, ...)
{// 1.从不定参中获取字符串char custom_log[1024] = {0};va_list v_li; // 本质是 char*va_start(v_li, fmt); // 将记录全部不定参数,到v_li中vsnprintf(custom_log, sizeof custom_log, fmt.c_str(), v_li); //通过接口转到char[]中va_end(v_li);print("%s%n", custom_log);}
C++不定参函数
// 模板函数 func 的定义
template <class T>
void func(const T& t)
{cout << t << endl;cout << endl;
}//非模板函数 func 的定义
void func()
{cout << 0 << endl;
}//1. 模板变参函数 func 的定义
template<class T , class ...Ags>
//void func(const T& t, Ags... args) // 一个一个地解包参数void func(const T& t, Ags&&... args) //
{cout << t << " 剩余包数:" << sizeof...(args) << endl ;//func(args...); // 剩余参数包func(forward<Ags>(args)...); //...的位置有讲究,
}//2. 使用变参为类传参
class MyClass {
public:MyClass(int a, int b, char _c) : x(a), y(b), c(_c) {}// 其他成员...
private:int x, y;char c;
};templete <class T, class ...Args>
void setclass(Args &&... args) {return std::make_shared<T>(std::forword<Args>(args)...);
// 假设 Args 是 int, int,char。不定参展开传参,展开示例如下:
//auto ptr = std::make_shared<MyClass>(std::forward<int>(10), std::forward<int>(20), std::forword<char>('k'));int main()
{func();func(1, 'A');func(1, 'A', "hello word");setclass<MyClass>("10", "20", 'k');
}
设计模式
设计模式是前辈们对代码开发经验的总结,是解决特定问题的⼀系列套路。它不是语法规定,⽽是⼀套⽤来提⾼代码可复⽤性、可维护性、可读性、稳健性以及安全性的解决⽅案。
设计原则:
从整体上来理解六⼤设计原则,可以简要的概括为⼀句话,⽤抽象构建框架,⽤实现扩展细节,具体到每⼀条设计原则,则对应⼀条注意事项:
- 单⼀职责原则告诉我们实现类要职责单⼀;
- ⾥⽒替换原则告诉我们不要破坏继承体系;
- 依赖倒置原则告诉我们要⾯向接⼝编程;
- 接⼝隔离原则告诉我们在设计接⼝的时候要精简单⼀;
- 迪⽶特法则告诉我们要降低耦合;
- 开闭原则是总纲,告诉我们要对扩展开放,对修改关闭。
这里只简单提及所用到的模式,详细了解设计模式,请自行寻找各模式的例子。
工厂模式
⼯⼚模式是⼀种创建型设计模式, 它提供了⼀种创建对象的最佳⽅式。在⼯⼚模式中,我们创建对象时不会对上层暴露创建逻辑,⽽是通过使⽤⼀个共同结构来指向新创建的对象,以此实现创建-使⽤的分离。()
⼯⼚模式可以分为:
简单⼯⼚模式: 简单⼯⼚模式实现由⼀个⼯⼚对象通过类型决定创建出来指定产品类的实例。假设有个⼯⼚能⽣产出⽔果,当客⼾需要产品的时候明确告知⼯⼚⽣产哪类⽔果,⼯⼚需要接收⽤⼾提供的类别信息,当新增产品的时候,⼯⼚内部去添加新产品的⽣产⽅式。
缺点:这个模式的结构和管理产品对象的⽅式⼗分简单, 但是它的扩展性⾮常差,当我们需要新增产品的时候,就需要去修改⼯⼚类新增⼀个类型的产品创建逻辑,违背了开闭原则。
⼯⼚⽅法模式: 在简单⼯⼚模式下新增多个⼯⼚,多个产品,每个产品对应⼀个⼯⼚。假设现在有A、B 两种产品,则开两个⼯⼚,⼯⼚ A 负责⽣产产品 A,⼯⼚ B 负责⽣产产品 B,⽤⼾只知道产品的⼯⼚名,⽽不知道具体的产品信息,⼯⼚不需要再接收客⼾的产品类别,⽽只负责⽣产产品。
缺点:⼯⼚⽅法模式每次增加⼀个产品时,都需要增加⼀个具体产品类和⼯⼚类,这会使得系统中类的个数成倍增加,在⼀定程度上增加了系统的耦合度。
抽象工厂模式:⼯⼚⽅法模式通过引⼊⼯⼚等级结构,解决了简单⼯⼚模式中⼯⼚类职责太重的问题,但由于⼯⼚⽅法模式中的每个⼯⼚只⽣产⼀类产品,可能会导致系统中存在⼤量的⼯⼚类,势必会增加系统的开销。
建造者模式
建造者模式是⼀种创建型设计模式, 使⽤多个简单的对象⼀步⼀步构建成⼀个复杂的对象,能够将⼀个复杂的对象的构建与它的表⽰分离,提供⼀种创建对象的最佳⽅式。主要⽤于解决对象的构建过于复杂的问题。
代理模式
代理模式指代理控制对其他对象的访问, 也就是代理对象控制对原对象的引⽤。在某些情况下,⼀个对象不适合或者不能直接被引⽤访问,⽽代理对象可以在客⼾端和⽬标对象之间起到中介的作⽤。
项目模块设计
日志框架设计
⽇志等级模块:对输出⽇志的等级进⾏划分,以便于控制⽇志的输出,并提供等级枚举转字符串功能。
- OFF:关闭
- DEBUG:调试,调试时的关键信息输出。
- INFO:提⽰,普通的提⽰型⽇志信息。
- WARN:警告,不影响运⾏,但是需要注意⼀下的⽇志。
- ERROR:错误,程序运⾏出现错误的⽇志
- FATAL:致命,⼀般是代码异常导致程序⽆法继续推进运⾏的⽇志
日志消息模块:存储日志必要的信息,如:时间,所在文件,行,日志等级,日志用户自定义部分,所在线程ID。
好了,我们有了组件日志的”材料“后开始制作日志,那我们该怎么将日志数据排列??
日志格式模块(format)
功能:用户给予日志格式化规则,如:%d%T[%t]%T[%p]%T[%c]%T%f:%l%T%m%n,日志格式器模块会生成的日志格式,然后日志依据格式构建日志信息,最后返回日志字符串。
设计模式:简单工厂模式
设计思想:
- 抽象出一个格式化子类的基类,同时声明虚函数fomat为子类形成日志的共同方法,由子类重写。
- 基于基类,派生出 时间,所在文件,行,日志等级等必要信息的子类;并重写format接口。
- 创建formatter 工厂类,根据用户规定的日志格式化规则,形成日志信息构建流水线(vector<format>)(实现方法:通过基类指针就能调用各个子类对象的fomat函数); 向外部提供获取日志字符串接口(GetResult)。
在外部构造日志时,直接调用formatter工厂类,由工厂类对外交互。
关系图:
format模块代码:
Log-Project/logs/format.hpp · 逆光/标准日志插件项目 - 码云 - 开源中国 (gitee.com)
日志落地模块(sink)
功能:用户给予一段日志信息,日志落地模块根据日志输出等级,将日志信息落地到输出到控制台,文件,滚动文件甚至是数据库。
设计模式:简单工厂模式
设计思路:
- 抽象出一个log落地基类,并同时声明log虚函数,让子类重写。
- 基于log基类,派生出控制台,文件,滚动文件(文件超过一定大小,自动创建新文件)等其他方向的子类,并根据落地方向不同重写log函数。
- 支持落地扩展。用户可以自己添加相应的落地方向模块,而工厂类创建不用修改。
- 创建logSink工厂类,向外部提供一个创建落地对象的接口createlogsink,用户即可通过传入落地方向的类,进行创建具体对象,让创建与表示分离。
代码:
Log-Project/logs/sink.hpp · 逆光/标准日志插件项目 - 码云 - 开源中国 (gitee.com)
日志管理器模块(logger)
功能:整合日志格式模块 & 日志管理器模块,向外提供根据日志等级输出的接口。
设计模式:工厂模式
管理成员:
- 日志器名称(日志器唯一标识符,在全局日志器中会对日志器进行管理)
- 日志格式化模块对象
- 日志落地方向对象数组(日志落地方向可能存在多个)
- 日志落地操作锁(在多线程情况下,保证日志写操作线程安全,避免日志交叉)
- 日志最小输出等级(小于该等级日志则不输出)
管理函数:
- debug,info,warn,fatal等级的日志输出操作(根据传入日志message使用日志格式化对象形成日志,然后调用日志落地接口log,完成日志落地)
- 抽象日志落地log
实现方式:
- 抽象logger基类(派生出 同步日志器 & 异步日志器 子类)
- 由于同步日志器异步日志器之间的区别是落地操作(log)的不同,因此我们将落地操作(log)抽象出来,在子类完成不同落地操作。最后通过logger基类指针,来调用不同日志器派生类重写的log操作。
代码:
Log-Project/logs/logger.hpp · 逆光/标准日志插件项目 - 码云 - 开源中国 (gitee.com)
扩展
功能:设计一个建造者类,简化用户设置日志参数,和用户使用日志器的复杂度。
设计模式:建造者模式
管理成员:由于是建造者类,其将管理成员为日志管理器的管理成员
管理函数:
- 对外提供设置日志器名称,日志器格式化规则,日志等级,创建日志落地方向等接口,一步步构造日志器部件。
实现方式:
- 抽象builderlogger类,提供日志器初始化部件接口(派生出 局部日志器建造者 & 全局日志器建造者 子类)
- 局部日志器与全局(单例)日志器建造者,区别在于前者无法突破作用域,而全局可以在程序的任一位置访问,并给予查看,管理,建造日志器。因此我们需要将builderlogger类中build抽象出来,让派生类来完成各自创建操作。
- 全局日志器建造者类需要使用单例模式,同时需要维护一个以名称查找的日志器unorder_map容器,以便于管理全局的日志器。
代码:
Log-Project/logs/builder.hpp · 逆光/标准日志插件项目 - 码云 - 开源中国 (gitee.com)
双缓冲区异步任务处理器(Asynlogger)
异步日志器相比与同步日志器,线程在调用日志器输出日志时,异步将日志信息存放在内存中然后继续业务逻辑,并不直接调用系统接口,进行IO操作;存在在内存中的日志信息会由异步日志落地线程专门来将日志信息落地。
设计思想:异步处理线程 + 数据池
使⽤者将需要完成的任务添加到任务池中,由异步线程来完成任务的实际执⾏操作。
任务池的设计思想:双缓冲区阻塞数据池
优势:避免了空间的频繁申请释放,且尽可能的减少了⽣产者与消费者之间锁冲突的概率,提⾼了任务处理效率。
问题:为什么不采用任务队列的方式处理日志?
答:
1. 任务队列锁冲突频繁,逻辑复杂。假设使用同一个任务队列,生产者(写日志线程)与生产者(写日志线程)存在锁冲突;生产者(写日志线程)与消费者(落地线程)存在锁冲突,锁冲突概率高。
2. 任务队列一般采用链表的方式,这样存在频繁的控制申请释放,空间无法复用。
⽽双缓冲区不同,双缓冲区是处理器将⼀个缓冲区中的任务全部处理完毕后,然后交换两个缓冲区,重新对新的缓冲区中的任务进⾏处理,虽然同时多线程写⼊也会冲突,但
是冲突并不会像每次只处理⼀条的时候频繁(⽣产者与消费者之间的锁冲突也只有交换缓冲区时,会有锁冲突),且不涉及到空间的频繁申请释放所带来的消耗。
缓冲区类设计
功能:提供缓冲区交换接口; 支持多日志一次落地; 支持缓冲区动态扩容
代码:
Log-Project/logs/buffer.hpp · 逆光/标准日志插件项目 - 码云 - 开源中国 (gitee.com)
异步日志器模块
异步⽇志器类继承⾃⽇志器类, 并在同步⽇志器类上拓展了异步消息处理器。当我们需要异步输出⽇志的时候, 需要创建异步⽇志器和消息处理器, 调⽤异步⽇志器的log、error、info、fatal等函数输出不同级别⽇志。
相对与同步日志器,异步日志器需要实现:
- 重写log函数,让日志信息写入到缓冲区中。
- 提供默认异步日志线程的缓冲区交换,读取缓冲区,最终完成日志落地功能逻辑的回调函数
代码:
Log-Project/logs/logger.hpp · 逆光/标准日志插件项目 - 码云 - 开源中国 (gitee.com)
日志宏
提供全局的⽇志器获取接⼝。 使⽤代理模式通过全局函数或宏函数来代理Logger类的log、debug、info、warn、error、fatal等接
⼝,以便于控制源码⽂件名称和⾏号的输出控制,简化⽤⼾操作。 当仅需标准输出⽇志的时候可以通过主⽇志器来打印⽇志。 且操作时只需要通过宏函数直接进⾏输出即可。(简化用户使用复杂度)
// 定义宏,用于自动获取文件名和行号,并且可以接受任意的日志记录器对象名,方便用户不用操作全局日志器
#define LOG_DEBUG(logger_name, fmt, ...) \GetLogger(logger_name)->Debug(__FILE__, __LINE__, fmt, ##__VA_ARGS__)#define LOG_INFO(logger_name, fmt, ...) \GetLogger(logger_name)->Info(__FILE__, __LINE__, fmt, ##__VA_ARGS__)#define LOG_FAIL(logger_name, fmt, ...) \GetLogger(logger_name)->Fail(__FILE__, __LINE__, fmt, ##__VA_ARGS__)#define LOG_WARN(logger_name, fmt, ...) \GetLogger(logger_name)->Warn(__FILE__, __LINE__, fmt, ##__VA_ARGS__)
详尽代码:
Log-Project/logs/log.h · 逆光/标准日志插件项目 - 码云 - 开源中国 (gitee.com)
性能测试
下⾯对⽇志系统做⼀个性能测试,测试⼀下平均每秒能打印多少条⽇志消息到⽂件。
主要的测试⽅法是:每秒能打印⽇志数 = 打印⽇志条数 / 总的打印⽇志消耗时间
主要测试要素:同步/异步 & 单线程/多线程。
测试项目:
- 1000w+条指定⻓度的⽇志输出所耗时间
- 每秒可以输出多少条⽇志
- 每秒可以输出多少MB⽇志
测试环境:
CPU: Intel(R) Xeon(R) Gold 6278C CPU @ 2.60GHz(2 核)
RAM: 4G
OS : CentOS Linux release 7.6.1810 (Core)
测试代码:
Log-Project/bench/bench.cxx · 逆光/标准日志插件项目 - 码云 - 开源中国 (gitee.com)
测试结果:打印相同日志数量,异步多线程最快;其次是同步多线程;
模块关系图
、
改bug心得
出错原因:对formatter功能理解有问题。
formatter功能:是根据用户给出的格式化规则,通过解析函数(pase_pattern),形成一个构造日志"队列"。
就好像:formatter是工厂,通过pase_pattern,自定义化形成一个构造日志流水线。
问题出在:我是如何理解这个流水线是:临时的(错误),还是持久化的(正确)。
首先从此次出错的问题:在多线程场景中,我的日志构造线的_items出现段错误。
出现问题思考解决思路:
刚开始时,我通过cout,定位到了出错点,同时也发现新线程来使用_items时出错。我刚开始意识到,这个_items出现线程安全,我后面甚至还想要不将_items从成员变量,做成临时变量,好让每次不用考虑加锁解锁。但这样做每发一个日志,就创建一个_items流水线工具,然后就丢弃,这样会有很大的性能浪费,在这里想就有问题,就没往这边想了。(差点在错误的路上一路狂奔)
最后,发现我应该将构造日志流水线做成持久化。从上到下讲,作为一个日志插件,用户在初始化日志器后,日志输出格式也应该确定了,日志构造流水线,也应处于被只读的情况,没有线程安全一说。
因此解析日志操作不能出现在,日志器构造日志中,应在初始化操作中。
结语
本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获,请动动你发财的小手点个免费的赞,你的点赞和关注永远是博主创作的动力源泉。
相关文章:
标准日志插件项目【C/C++】
博客主页:花果山~程序猿-CSDN博客 文章分栏:项目日记_花果山~程序猿的博客-CSDN博客 关注我一起学习,一起进步,一起探索编程的无限可能吧!让我们一起努力,一起成长! 目录 一,项目介…...
SpingBoot原理
SpingBoot原理 在前面十多天的课程当中,我们学习的都是web开发的技术使用,都是面向应用层面的,我们学会了怎 么样去用。而我们今天所要学习的是web后端开发的最后一个篇章springboot原理篇,主要偏向于底 层原理。 我们今天的课程…...
Cout输出应用举例
Cout输出应用 在main.cpp里输入程序如下: #include <iostream> //使能cin(),cout(); #include <stdlib.h> //使能exit(); #include <sstream> #include <iomanip> //使能setbase(),setfill(),setw(),setprecision(),setiosflags()和res…...
java的无锁编程和锁机制
Java 的并发编程中,为了保证线程安全和高性能,采用了两种主要的同步手段:锁机制和无锁编程。以下是对锁机制、无锁编程、死锁及其避免的详细讲解。 一、无锁编程 无锁编程通过原子操作来避免传统锁,从而减少线程的上下文切换&am…...
vue实现富文本编辑器上传(粘贴)图片 + 文字
vue实现富文本编辑器上传(粘贴)图片 文字 1.安装插件 npm install vue-quill-editor -s2.在使用vue-quill-editor富文本的时候,对于图片的处理经常是将图片转换成base64,再上传数据库,但是base64不好存储。 原理&a…...
子集和全排列(深度优先遍历)问题
欢迎访问杀马特主页:小小杀马特主页呀! 目录 前言: 例题一全排列: 1.题目介绍: 2.思路汇总: 3.代码解答: 例题二子集: 题目叙述: 解法一: 1.思路汇总…...
判断检测框是否在感兴趣区域(ROI)内
判断检测框是否在感兴趣区域(ROI)内 在计算机视觉和图像处理中,我们经常需要确定一个矩形检测框是否位于一个特定的感兴趣区域(Region of Interest, ROI)内。这个ROI可以是一个多边形,而检测框则是一个矩形…...
正点原子阿尔法ARM开发板-IMX6ULL(九)——关于SecureCRT连接板子上的ubuntu
文章目录 一、拨码器二、SecureCRT 一、拨码器 emmm,也是好久没学IMX6ULL了,也是忘了拨码器决定了主板的启动方式 一种是直接从TF卡中读取文件(注意这里是通过imdownload软件编译好了之后,通过指令放入TF卡) 一种是现在这种用串口…...
微信支付Java+uniapp微信小程序
JS: request.post(/vip/pay, {//这是自己写的java支付接口id: this.vipInfo.id,payWay: wechat-mini}).then((res) > {let success (res2) > {//前端的支付成功回调函数this.$refs.popup.close();// 支付成功刷新当前页面setTimeout(() > {this.doGetVipI…...
【NOIP提高组】加分二叉树
【NOIP提高组】加分二叉树 💐The Begin💐点点关注,收藏不迷路💐 设一个n个节点的二叉树tree的中序遍历为(l,2,3,…,n),其中数字1,2,3,…,n为节点编号。每个节点都有一个分数(均为正整…...
HarmonyOS 相对布局(RelativeContainer)
1. HarmonyOS 相对布局(RelativeContainer) 文档中心:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-layout-development-relative-layout-V5 RelativeContainer为采用相对布局的容器,支持容器内部的子元素设…...
webpack5搭建react脚手架详细步骤
1. 初始化项目 首先,创建一个新目录并初始化项目: bash mkdir create-react cd create-react pnpm init --y git init 这里使用pnpm作为包管理工具,因为它在处理依赖和速度上表现更好。 2. 安装React和TypeScript 安装React和React-DOM…...
速盾:高防cdn怎么拦截恶意ip?
高防CDN(Content Delivery Network)是一种用于防御网络攻击和提供高可用性的服务。它通过分发网络流量,将用户的请求导向最近的服务器,从而提高网站的加载速度和稳定性。然而,不可避免地,有些恶意IP地址会试…...
太阳能面板分割系统:训练自动化
太阳能面板分割系统源码&数据集分享 [yolov8-seg-EfficientHead&yolov8-seg-vanillanet等50全套改进创新点发刊_一键训练教程_Web前端展示] 1.研究背景与意义 项目参考ILSVRC ImageNet Large Scale Visual Recognition Challenge 项目来源AAAI Globa…...
C++笔记---位图
1. 位图的概念 位图(Bitmap)是一种基于位操作的数据结构,用于表示一组元素的集合信息。它通常是一个仅包含0和1的数组,每个元素对应一个二进制位,若该元素存在,则对应的位为1;若不存在ÿ…...
ABC370
## A - Raise Both Hands (模拟) 题意:输入l,r,如果l1r0输出yes,l0r1输出no,否则输出Invalid 代码: #include<bits/stdc.h> using namespace std; typedef long long ll; vo…...
C语言[求x的y次方]
C语言——求x的y次方 这段 C 代码的目的是从用户输入获取两个整数 x 和 y ,然后计算 x 的 y 次幂(不过这里有个小错误,实际计算的是 x 的 (y - 1) 次幂,后面会详细说),最后输出结果。 代码如下: #include…...
JavaScript part2
一.前言 前面我们讲了一下js的基础语法,但是这些还是远远不够的,我们要想操作标签,实现一个动态且好看的页面,就得学会BOM和DOM,这些都是浏览器和页面的,这样我们才能实现一个好看的页面 二.BOM对象 BOM…...
HarmonyOS开发 - 本地持久化之实现LocalStorage实例
用户首选项为应用提供Key-Value键值型的数据处理能力,支持应用持久化轻量级数据,并对其修改和查询。数据存储形式为键值对,键的类型为字符串型,值的存储数据类型包括数字型、字符型、布尔型以及这3种类型的数组类型。 说明&#x…...
【C++打怪之路Lv12】-- 模板进阶
#1024程序员节|征文# 🌈 个人主页:白子寰 🔥 分类专栏:重生之我在学Linux,C打怪之路,python从入门到精通,数据结构,C语言,C语言题集👈 希望得到您…...
第23周Java主流框架入门-SpringMVC 2.RESTful开发风格
课程笔记:RESTful 开发风格 课程介绍 本节课程介绍 RESTful 开发风格,以及如何在 Spring MVC 中应用这种开发模式。传统 MVC 开发通过 Servlet、JSP 和 Java Bean 实现前后端交互,而 RESTful 开发提供了一种新的理念,更适合现代…...
QT枚举类型转字符串和使用QDebug<<重载输出私有枚举类型
一 将QT自带的枚举类型转换为QString 需要的头文件: #include <QMetaObject> #include <QMetaEnum> 测试代码 const QMetaObject *metaObject &QImage::staticMetaObject;QMetaEnum metaEnum metaObject->enumerator(metaObject->indexOf…...
手机柔性屏全贴合视觉应用
在高科技日新月异的今天,手机柔性显示屏作为智能手机市场的新宠,以其独特的可弯曲、轻薄及高耐用性特性引领着行业潮流。然而,在利用贴合机加工这些先进显示屏的过程中,仍面临着诸多技术挑战。其中,高精度对位、应力控…...
《Python游戏编程入门》注-第3章3
《Python游戏编程入门》的“3.2.4 Mad Lib”中介绍了一个名为“Mad Lib”游戏的编写方法。 1 游戏玩法 “Mad Lib”游戏由玩家根据提示输入一些信息,例如男人姓名、女人姓名、喜欢的食物以及太空船的名字等。游戏根据玩家输入的信息编写出一个故事,如图…...
Netty-TCP服务端粘包、拆包问题(两种格式)
前言 最近公司搞了个小业务,需要使用TCP协议,我这边负责服务端。客户端是某个设备,客户端传参格式、包头包尾等都是固定的,不可改变,而且还有个蓝牙传感器,透传数据到这个设备,然后通过这个设备…...
centos安装指定版本的jenkins
打开jenkins镜像包官网,找到自己想要安装的版本,官网地址:https://mirrors.tuna.tsinghua.edu.cn/jenkins/redhat-stable 下载指定版本安装包: wget https://mirrors.tuna.tsinghua.edu.cn/jenkins/redhat-stable/jenkins-2.452.…...
QT 周期性的杀死一个进程(软件),一分钟后自动退出
1.原因:某软件开机自启动很烦,搞一个程序干掉这个自启动的软件 2.QT代码 main.cpp #include "KillXXX.h" #include <QtWidgets/QApplication>int main(int argc, char *argv[]) {QApplication a(argc, argv);KillXXX k;return a.exec…...
MySQL任意版本安装卸载和数据库原理图绘制
MYSQL任意版本安装和卸载 安装: 1、解压文件 --- 不能出现中文路径 2、在解压目录(安装目录)下: 1>.创建data文件夹 2>.创建配置文件my.txt 然后修改成ini格式 3、修改配置文件 basedirD:\\mysql\\mysql-5.7.28-winx64…...
技术成神之路:设计模式(二十三)解释器模式
相关文章:技术成神之路:二十三种设计模式(导航页) 介绍 解释器模式(Interpreter Pattern)是一种行为设计模式,用于定义一种语言的文法表示,并提供一个解释器来处理这种文法。它用于处理具有特定语法或表达…...
2024软考《软件设计师》-Python专题知识(含历年真题解析)
自2020年之后,软考软件设计师考试在综合知识部分开始增加Python编程语言相关考点,每年会考2~3分的样子。本文将结合近几年常考的内容,扩展一下Pyhton的基础知识!考前看一看,或许有所帮助。 一、基础语法 标识符 第一…...
wp博客怎么改wordpress/洛阳搜索引擎优化
Kettle8.2转换组件之计算器一、相关说明二、设计转换三、转换配置四、运行转换五、结果分析一、相关说明 需求说明: 从Excel读取数据,将其中一些字段进行计算成目标数值,并把结果数据保存在Excel中。计算器组件说明: 计算器是一个…...
做背景图获取网站/附子seo教程
【杠精学物理】第267篇原创文章。今天视频要讲的是一个高考中常见的问题——欧姆表的误差分析(当电源电动势降低,内阻增大时,测量值与真实值差异问题)。问题来源于前两天看到学生群里的讨论,感觉同学们越辩越糊涂。在此录制一个视频ÿ…...
wordpress4.2.15漏洞/seo快速排名利器
数据从业者常在多种工具之间跳来跳去,这种碎片化导致了协作、共享和生产力方面的问题。 企业云数据量的增加以及数据转换、模型构建和可视化工具的出现,推动了现代数据堆栈的崛起。大部分公司都在加大对数据团队的投入,以适应不断变化的需求…...
公司网站主机流量30g每月够用吗/小企业广告投放平台
2019独角兽企业重金招聘Python工程师标准>>> 在某些状况下,类内成员变量需要动态开辟堆内存,如果实行位拷贝,也就是把对象里的值完全复制给另一个对象,如AB。这时,如果B中有一个成员变量指针已经申请了内存…...
sever2012做网站/中国最好的营销策划公司
在创建python包的过程中,IDE都会在包根目录下创建一个__init__.py文件,该Python文件默认是空的.目录结构如下: Pycharm下的package树结构: 在Finder中的目录结构: 从Finder中的目录就可以看出来,每个packag…...
男人和女人做受吃母乳视频网站免费/南宁百度seo公司
python 中时间格式转换 import time, datetime时间戳 时间戳转时间 timestamp time.time() # 当时时间下的时间戳 zerotimestamp datetime.datetime.utcfromtimestamp(time.time()) # 当时时间戳下巴黎时间计时的时间戳在时间戳上利用秒计时来实现时间的加减, …...