分类 编程及辅助 下的文章

CMake:用add_subdirectory()添加外部项目文件夹


需求

正常情况下我们的项目各个子项目都在一个总的项目根目录下,但这次就需要使用外部的文件夹, 一个求解器文件夹(不动其代码),一个测试用例的相关项目,测试用例调用求解器内部代码进行相关测试。

处理过程

add_subdirectory命令,可以将指定的文件夹加到build任务列表中。下面是将与当前项目平级的一个目录下的子目录用add_subdirectory命令加进来的CMakelists.txt脚本片段 :

add_subdirectory(../test/test)

然后执行CMakeList.txt脚本时报错了:

屏幕截图 2022-06-13 114712.png

报错原因也很明确,因为要添加的这个文件夹不是当前项目的子目录。最后一句指明了方向:要指定一个外部的文件夹必须显式指定。
但怎么显式指定?
下面是add_subdirectory命令的官方说明:

官方文档

add_subdirectory有一个 binary_dir参数,这个参数用来指定source_dir在输出文件夹中的位置,如果没有指定的时候,就用source_dir的值。
如果要添加外部文件夹,binary_dir就必须指定。所以上面的代码修改如下:

add_subdirectory(../test/test test.out)

再执行CMakeList.txt正常通过,下图红框标出就是cassdk子目录下CMakeLists.txt的执行输出。


C++: 自定义错误码类,实现归拢错误信息。


需求

求解器老报错,还得找错误在哪,其实大多数都是一些属性没配置才导致报错,在报错的地方throw出来就能知道具体是哪些错误,把错误编码,这样就能把类似宏这种的返回编码,然后根据编码能查到具体错误信息。

用法

// 定义相关错误
static Error E_NOT_FOUND_PTR(100010001,"未发现指针");

// 响应相关错误
throw E_NOT_FOUND_PTR;

// 查询相关错误并输出
try{
    // 生产代码
} catch (Error info) {
    cout << "[ERROR] [" << info << "] [" << Error::GetErrorString(info) << "]" << endl;
}

类代码

#include <string>
#include <map>
#include <cassert>

class Error
{
public:
    Error(int value, const std::string& str)
    {
        m_value = value;
        m_message =    str;
        #ifdef _DEBUG
        ErrorMap::iterator found = GetErrorMap().find(value);
        if (found != GetErrorMap().end())
        assert(found->second == m_message);
        #endif
        GetErrorMap()[m_value] = m_message;
    }

    operator int() { return m_value; }

private:
    int m_value;
    std::string m_message;

    typedef std::map<int, std::string> ErrorMap;
    static ErrorMap& GetErrorMap()
    {
        static ErrorMap errMap;
        return errMap;
    }

public:

    static std::string GetErrorString(int value)
    {
        ErrorMap::iterator found = GetErrorMap().find(value);
        if (found == GetErrorMap().end())
        {
            assert(false);
            return "";
        } else {
            return found->second;
        }
    }
};

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