DevOps:加速软件交付和提高团队协作的关键


在现代软件开发中,DevOps已经成为一种流行的方法论。它不仅可以加速软件交付,还可以提高开发团队的协作效率。本文将介绍DevOps的基本概念、核心原则以及一些实践方法。

DevOps的基本概念

DevOps是Development(开发)和Operations(运维)的结合词。它强调开发团队和运维团队之间的紧密合作,通过自动化和持续交付的方式,实现快速、可靠的软件交付。DevOps的目标是缩短软件开发周期,提高软件质量,并增强团队的协作效率。

DevOps的核心原则

DevOps的核心原则包括持续集成、持续交付和持续部署。持续集成指的是开发人员频繁地将代码合并到共享代码库,并通过自动化的构建和测试流程,确保代码的质量和稳定性。持续交付则是指将经过测试的代码部署到生产环境的能力,以便快速响应用户需求。持续部署则更进一步,指的是将代码自动部署到生产环境,实现全自动化的软件交付。

DevOps的实践方法

为了实现DevOps的目标,团队可以采用一些实践方法。首先,使用版本控制系统来管理代码,并建立自动化的构建和测试流程。其次,采用容器化技术,如Docker,来实现应用程序的快速部署和扩展。此外,引入持续集成和持续交付工具,如Jenkins和GitLab CI,可以帮助团队实现自动化的构建、测试和部署。最后,通过监控和日志分析工具,如Prometheus和ELK Stack,可以实时监测应用程序的性能和稳定性。

DevOps作为一种流行的方法论,已经在许多组织中取得了成功。通过加速软件交付和提高团队协作效率,DevOps可以帮助组织更好地满足用户需求,并在竞争激烈的市场中保持竞争优势。然而,实施DevOps并不是一蹴而就的过程,需要团队的共同努力和持续改进。只有不断学习和适应新的技术和方法,才能实现DevOps的最大价值。

希望能够帮助你了解DevOps的基本概念、核心原则和实践方法。如果你有任何问题或需要进一步的信息,请随时提问。


C++: 在Visual Studio里利用pdb文件进入lib调试方法


1. 基础知识

静态库和动态库

首先说一下,库的导出分为静态库和动态库。
20210701152123277.png

导出静态库的话,会生成一个.lib文件,其实就是.obj的集合,.obj又是.cpp编译得到的,所以,静态库里有函数的实现。不过这些实现是二进制的,是不带源码的。使用静态库的时候,需要链接.lib,包含.h。

导出动态库的话,会生成.dll文件和.lib文件,动态库生成的.lib文件会很小,因为这里的.lib只需要指明函数在.dll中的位置就可以了。使用的时候,需要把.dll和程序将要生成的.exe放在一个位置(双击exe运行),或者放在“调试 - 工程目录”指定的位置(在IDE中运行)。当然,如果你把“调试 - 工程目录“设置成$(OutDir),就和exe的输出位置一致了,调试也能运行,双击exe也能运行。

符号文件.pdb

.pdb文件就是所谓的符号文件。在库的编译过程中一同产生,pdb的生成位置和文件名由“链接器 - 调试 - 生成程序数据库文件”决定。这个名字是会写进.dll的,也可以和.dll的名字不一样。例如,dll的名字是a.dll,生成时的符号文件是b.pdb,你在调试时把b.pdb强行改成a.pdb,调试器是不认的。所以,为了调试方便不混淆,我建议把.pdb的文件名设置成和.dll一样。否则debug版和release版的pdb都一个名字,就无法区分了。

.pdb文件里面记载了函数名称和对应的.cpp文件名称,.cpp文件的md5信息。所谓“符号文件”,就是在你调试时用来查找函数对应于哪个.cpp文件哪一行的,好告知调试器跳转过去。
2021070115362282.png

2. 加载符号

现在假设你已经有一个工程了,可以编译运行。你调用了在.lib中的函数,下了断点,按F11想要步入。

这时有两种情况,第一种是你直接跳转到库的源码了。这里的原理很简单,因为你的pdb已经加载(你把pdb放在调试目录了),pdb里记载了函数所在的cpp,而这个库是你在本机编译的,绝对路径没有改动,调试器直接就找到了对应的cpp,所以就直接跳转了。

而第二种情况,按F11并不能进入到源码,而是直接步过了。如果想进入.lib的源码,需要怎么做呢?

首先需要准备.pdb文件,还有库的源码。

之后要检查符号是否加载。下断点,F5调试。然后“<font color=red>调试 - 窗口 - 模块</font>”打开模块窗口。

20210701153351397.png

找到第三方dll的名字,我这里是DuiLib_ud.dll,看“符号文件”一栏是空的。说明这个dll的符号文件没有加载。

把.pdb和.dll放在一起。

20210701154550416.png

再次调试。可以看到符号文件已经加载了。

20210701154633902.png

其实不和dll放一起也可以。“工具 - 选项 - 符号”,在符号文件位置下面增加路径也可以。不过我不喜欢改动没有移植性的环境选项,更喜欢凡事都用相对路径。顺带一提,通过上面的模块界面,可以加载系统dll的符号文件,调试时可以进入系统dll的汇编。

20210701154723632.png

3. 加载源码

现在符号文件已经加载了。再次开始调试,在断点处按F11步入。弹出了查找源码的选项。

20210701155426186.png

在这里你就可以手动指定源码的位置了,不用担心版本出错,有md5校验的,你指定的.cpp如果不是这个库当初生成时的.cpp是通不过的。

不过我不喜欢这种硬编码绝对路径的做法,不优雅。我们点取消,就会看见未找到XXX.cpp界面。展开“源搜索信息”,可以看到调试器都从哪些地方在寻找这个.cpp文件。记住这个界面,如果我们之后指定.cpp路径时不正确,可以在这里来查看。

20210701155713480.png

之后,把库的源码复制到我们自己的工程目录里。工程文件什么的不用带,只需要.cpp就可以了,理论上.h都不需要。

20210701161411580.png

之后,在解决方案上右键“属性”,注意是解决方案上右键,不是工程。选择“通用属性 - 调试源文件”,在“包含源代码的目录”里增加我们刚拷进来的库的源代码目录。

20210701161439586.png

微软说这里的查找源文件功能是不能识别子目录的,所有子目录都要手动加进来。

这里修改了之后,我实验发现要重启VS才生效。再次调试,可以进到库的源码了。在库的源码上悬停,我这里是在UIBase.cpp上悬停,可以看到它找到的源码的文件路径。确认是找到了我们刚放进去的源码。

20210701161729269.png

本来我还想实验一下。我改了lib_src文件夹的名字,发现它还是能找到源码;我删掉debug文件夹下的内容,它还是能找到;我删掉和exe一块儿生成的.ilk和.pdb,它还是能找到;我删掉.vs隐藏文件夹,它还是能找到;我改动工程路径,它还是能找到。

这就很迷茫了。总之,也许它找到一次之后,查找文件功能就突然逆天了,知道寻找解决方案下面的所有cpp文件吧。

参考

如何调试静态库的代码(libeay32.lib和ssleay32.lib)

在 Visual Studio 调试器(C#、C++、Visual Basic、F#)中指定符号 (.pdb) 和源文件


C/C++: 命令行下的软件安装进度条


前言

我们在平时的服务器运维工作中,要经常安装一些软件,经常会看到下面这种进度条,咱们就用C语言来实现这种进度条。

相关知识

  1. 用fflush函数强行让屏幕刷新。
  2. printf格式控制
  3. stdout即屏幕的文件流

截图

cmd_loading.gif

程序代码包

cmd_loading.zip

相关代码

#include <stdio.h>
#include <string.h>
#include <ctime>
#include <windows.h>        // window下用Sleep
// #include <unistd.h>      // linux下用sleep

void loader(int rate)
{
    char proc[102];
    memset(proc, '\0', sizeof(proc));
    
    for (int i = 0; i < rate; i++)
    {
        proc[i] = '#';
    }
    
    printf("[%-100s] [%d%%]\r", proc, rate);        //C语言格式控制时默认右对齐,所以要在前面加-变成左对齐
    fflush(stdout);                                 //刷新屏幕打印
}

int main()
{
    int i = 0;
    while(i <= 100)    
    {
        loader(i);
        Sleep(200);                                 //以微秒为单位的sleep
        i++;  
    }
    return 0;
}

算法(C++): 自适应辛普森(Simpson)积分和二重积分算法


概述

最近matlab中存在个二重积分的函数,携程c++的就给我整懵了,因为方法比较多,很容易结果对不上,之前的数值积分是用的龙贝格积分法,应该是前十位相同。这回辛普森的精确到9位,大概是没啥大问题。
辛普森积分法利用拉格朗日插值法来近似拟合原函数,然后通过对拟合函数的积分来简化近似。
近似公式如下:

$$ \int_{a}^{b} f(x) \mathrm{d} x \approx \frac{b-a}{6}\left(f(a)+4 f\left(\frac{a+b}{2}\right)+f(b)\right. $$

拉格朗日插值

拉格朗日插值利用二次曲线(抛物线)来拟合。
在求通过点集$(x_{1},y_{1})$,$(x_{2},y_{2})$,$(x_{3},y_{3})$,···,$(x_{n},y_{n})$的曲线上,拉格朗日利用多条二次曲线来拟合。首先二次曲线 $g = f(x)$ 满足

$$ f_{i}(x_{i}) \begin{cases} 1 \quad i = j\\ 0 \quad i \neq j\\ \end{cases} $$

因此$y_{n}f_{n}(x)$ 在$x_{n}$处,取值$y_{n}$,其余点取值为0。

拉格朗日通过对$f_{i}(x)$的构造如下:

$$ f_{i}(x)=\prod_{j=i}^{1 \leq j \leq n} \frac{\left(x-x_{j}\right)}{\left(x_{i}-x_{j}\right)} $$

最终得到了:

$$ f(x)=\sum_{i=1}^{n} y_{i}f_{i}(x) $$

辛普森公式

积分式:

$$ \int_{a}^{b} f(x)dx $$

第一我们需要用拉格朗日插值法对 $f(x)$ 进行近似计算,得出:

$$ f(x) \approx L_{n}(x) $$

$$ L_{n} = \sum_{i=1}^{n} y_{i}p_{i}(x) $$

$$ p_{i}(x)=\prod_{j=/i}^{1 \leq j \leq n} \frac{\left(x-x_{j}\right)}{\left(x_{i}-x_{j}\right)} $$

进而得出:

$$ f(x) \approx L_{n}(x) = \sum_{i=1}^{n} y_{i}p_{i}(x) $$

两边积分从而得出:

$$ \int_{a}^{b} f(x)dx \approx \int_{a}^{b} \sum_{i=1}^{n} y_{i}p_{i}(x) dx = \sum_{i=1}^{n} y_{i} \int_{a}^{b} p_{i}(x) dx $$

公式中的 $y_{i}=f(x_{i})& 。 令

$$ A_{i} = \int_{a}^{b} p_{i}(x) dx $$

那么就有:

$$ \int_{a}^{b} f(x)dx \approx \sum_{i=1}^{n} A_{i} f(x_{i}) $$

此外,如果在区间[a,b]中取n个点来拟合或者近似,则有 $h=\frac{b-a}{n}$,$X_{k}=a+kh$ , 所以:

$$ A_{i} = \int_{a}^{b} p_{i}(x) dx = h \int_{n}^{0} \prod_{j=1}^{n} \frac{(t-j)}{i-j}dt=(b-a)C_{a}^{(n)} $$

进而就有了:

$$ \int_{a}^{b} f(x)dx \approx \sum_{i=1}^{n} y_{i} A_{i} = (b-a)\sum_{i=0}^{n} C_{a}^{(n)} f(x_{i}) $$

该公式为牛顿-科特斯求积公式。
如果取3个点$x_{1}=a,x_{2}=\frac{a+b}{2},x_{3}=b$,则有下面:

$$ C_{0}^{(2)} = \frac{1}{6}, C_{1}^{(2)} = \frac{4}{6}, C_{2}^{(2)} = \frac{1}{6} $$

$$ \int_{a}^{b} f(x)dx \approx \frac{b-a}{6} (f(a)+4f(\frac{a+b}{2})+f(b)) $$

自适应辛普森积分

在辛普森公式中,三点的近似精度可能满足不了工程要求得精度,为此自适应辛普森积分会根据实际情况来自动调整精度。误差大的区域会划分多个区域。具体判断准则为:

$$ |S(a,c)+S(c,b)-S(a,b)| < 15 * eps $$

其中a , b为积分上下限,$c=\frac{a+b}{2}$,eps为精度

相关代码如下:

#include<iostream>
#include<stdlib.h>
using namespace std;
double Fun(double x){
    return x*x+x;//积分函数,自定义函数
}
double simpson(double a, double b){
   double c=(b+a)/2.0;
   return (Fun(a)+4*Fun(c)+Fun(b))*(b-a)/6.0;//求辛普森近似值
}
double adsp(double a,double b, double eps, double S){//自适应辛普森递归过程
    double c=(b+a)/2.0;
    double L=simpson(a,c),R=simpson(c,b);
    if(abs(L+R-S)<=15.0*eps){//判断准则
        return L+R+(L+R-S)/15.0;
    }
    return adsp(a,c,eps/2.0,L)+adsp(c,b,eps/2.0,R);
}
double intergation(double a, double b, double eps){//求积分
    return adsp(a,b,eps,simpson(a,b));
}
int main() {
    cout<<intergation(0, 1, 0.00001)<<endl;
    system("PAUSE");
    return 0;
}

二重积分

二重积分公式:

$$ \int_{a}^{b}\int_{c}^{d}f(x,y)dx dy = \int_{a}^{b}F_{x}(y)dy \approx \frac{b-1}{6}(F_{x}(a)+4F_{x}(\frac{a+b}{2})+F_{x}(b)) $$

$$ F_{x}(y) = \int_{d}^{c}f(x,y)dx \approx \frac{d-c}{6}(f(c,y)+4f(\frac{d+c}{2},y)+f(d,y)) $$

二重积分只要根据辛普森公式固定y的值后,对f ( x , y ) f(x,y)f(x,y)求关于x的积分,之后再对y求积分就ok了。

相关代码如下:
CommonAlgorithm.h

/**
 * @file CommonAlgorithm.h
 * @author ybw (root@bug-maker.com)
 * @brief 公共算法头文件
 * @version 0.1
 * @date 2022-01-20
 * 
 * @copyright Copyright (c) 2022 CIOMP
 * 
 */
#ifndef _COMMON_ALGORITHM_H_
#define _COMMON_ALGORITHM_H_

#include <iostream>
#include <string>

#ifndef M_PI
#define M_PI 3.1415926535897932384626433832795
#endif

typedef double(*cbp)(double);
typedef double(*cbp2)(double,double);

namespace CommonAlgorithm {
    /**
     * @brief 二重积分函数(对matlab)
     * 
     * @param cbp2 回调函数
     * @param xa x上限
     * @param xb x下限
     * @param ya y上线
     * @param yb y下限
     * @param eps 步长
     * @return double 
     */
    double integral2(double (* F)(double x, double y), double xa, double xb,double ya, double yb,double eps = 1.0e-8);
    double simpsonX2(double (* F)(double x, double y), double a, double b,double y);
    double adspX2(double (* F)(double x, double y), double a,double b,double y, double eps, double S);
    double inte2(double (* F)(double x, double y), double a, double b,double y, double eps);
    double simpsonY2(double (* F)(double x, double y), double xa, double xb,double ya, double yb,double eps);
    double adspY2(double (* F)(double x, double y), double xa, double xb,double ya, double yb, double eps,double S);
    double intergation2(double (* F)(double x, double y),double xa, double xb,double ya, double yb,double eps);
}

#endif

CommonAlgorithm.cpp

/**
 * @file CommonAlgorithm.cpp
 * @author ybw (root@bug-maker.com)
 * @brief 公共算法源文件
 * @version 0.1
 * @date 2022-01-20
 * 
 * @copyright Copyright (c) 2022 CIOMP
 * 
 */
#include "CommonAlgorithm.h"

double CommonAlgorithm::simpsonX2(double (* vFunction)(double x, double y), double a, double b,double y){//a,d为积分下、上限,y为被固定的y值
   double c=(b+a)/2.0;
   return (vFunction(a,y)+4*vFunction(c,y)+vFunction(b,y))*(b-a)/6.0;//对x的辛普森近似
}

double CommonAlgorithm::adspX2(double (* vFunction)(double x, double y), double a,double b,double y, double eps, double S){//对x的自适应辛普森递归
    double c = (b + a) / 2.0;
    double L = CommonAlgorithm::simpsonX2(vFunction,a,c,y),R=CommonAlgorithm::simpsonX2(vFunction,c,b,y);
    if(abs(L+R-S)<=15.0*eps){
        return L+R+(L+R-S)/15.0;
    }
    return CommonAlgorithm::adspX2(vFunction,a,c,y,eps/2.0,L)+CommonAlgorithm::adspX2(vFunction,c,b,y,eps/2.0,R);
}
double CommonAlgorithm::inte2(double (* vFunction)(double x, double y), double a, double b,double y, double eps){//固定y后,对x的积分
    return CommonAlgorithm::adspX2(vFunction,a,b,y,eps,CommonAlgorithm::simpsonX2(vFunction,a,b,y));
}
double CommonAlgorithm::simpsonY2(double (* vFunction)(double x, double y), double xa, double xb,double ya, double yb,double eps){
    double yc=(ya+yb)/2.0;
    return (CommonAlgorithm::inte2(vFunction,xa,xb,ya,eps)+4*CommonAlgorithm::inte2(vFunction,xa,xb,yc,eps)+CommonAlgorithm::inte2(vFunction,xa,xb,yb,eps))*(yb-ya)/6.0;//对y的辛普森近似
}
double CommonAlgorithm::adspY2(double (* vFunction)(double x, double y), double xa, double xb,double ya, double yb, double eps,double S){//对y的自适应辛普森递归
    double L=CommonAlgorithm::simpsonY2(vFunction,xa,xb,ya,(ya+yb)/2,eps);
    double R=CommonAlgorithm::simpsonY2(vFunction,xa,xb,(ya+yb)/2,yb,eps);
    if(abs(L+R-S)<=15.0*eps){
        return L+R+(L+R-S)/15.0;
    }
    return CommonAlgorithm::adspY2(vFunction,xa,xb,ya,(ya+yb)/2.0,eps/2.0,L)+CommonAlgorithm::adspY2(vFunction,xa,xb,(ya+yb)/2.0,yb,eps/2.0,R);
}
double CommonAlgorithm::intergation2(double (* vFunction)(double x, double y),double xa, double xb,double ya, double yb,double eps){//求二重积分
    return CommonAlgorithm::adspY2(vFunction,xa,xb,ya,yb,eps,CommonAlgorithm::simpsonY2(vFunction,xa,xb,ya,yb,eps));
}

double CommonAlgorithm::integral2(double (* vFunction)(double x, double y), double xa, double xb,double ya, double yb,double eps){
    return CommonAlgorithm::adspY2(vFunction,xa,xb,ya,yb,eps,CommonAlgorithm::simpsonY2(vFunction,xa,xb,ya,yb,eps));
}

Qt: QToolButton颜色选择器


一、概述

项目中需要到了颜色选择器,还不能直接用QColorDialog,必须得有个中间过程,那就只能单独写,上网查了查,没有个正经的dome,自己动手丰衣足食。

二、演示

colorSelecter.gif

三、demo下载

qt_demo.zip (包含了上次的表格相关代码)

四、代码解析

解析先写在注释里,有时间再做解释。

// 封装颜色选择器
class ColorComboBoxAPI ColorComboBox : public QToolButton
{
    Q_OBJECT

public:
    ColorComboBox(QWidget *parent = 0);
    ~ColorComboBox();
    // 设置图标(待处理)
    void setButtonIcon(const QString &imageFile, QColor color);

private:
    // 创建下拉菜单
    QMenu* createColorMenu(const char *slot, const char *slotColorBoard);
    // 创建工具按钮图标(待处理)
    QIcon createColorToolButtonIcon(const QString &imageFile, QColor color);
    // 创建颜色的像素图
    QIcon createColorIcon(QColor color);
    // 根据颜色选择器大小来创建像素图
    QIcon setColorIcon(QColor color);

signals:
    // 颜色改变发的信号
    void sigColorChanged(QColor color);

private slots:
    // 颜色选择
    void OnColorChanged();
    // 显示调色板
    void OnShowColorBoard();
};

五、碰到的问题

显示调色板后的英文问题,不显示中文肯定是不行滴,网上简单搜罗搜罗就找到了办法,但是还是有一点小瑕疵。。中午需要引入qt_zh_CN.qm来做翻译,代码如下,直接加到main里就行。

QTranslator trans;
trans.load("qt_zh_CN.qm","D:\\ProgramFiles\\Qt\\Tools\\QtCreator\\share\\qtcreator\\translations");
app.installTranslator(&trans);

这里的trans.load("qt_zh_CN.qm",qm目录)大家会有疑问,这目录是哪来的,这个是qt自带的,qt的安装目录下就可以找到,拾取的部分还是没有翻译,如果需要的话还是再百度一下吧,别人都是ts文件改了之后转qm,然后再引入。

六、总结

颜色选择器还是挺简单的,不需要太多的代码,其实挺多也都是百度的,就是在这里记录一下。