第十章g2o_custombundle中的common文件夹中的flags文件夹下的command_args.cpp

本文主要解读g2o_custombundle中common/flags目录下的command_args.cpp文件,关注类成员函数的定义及其常用操作。部分内容仍有待进一步细化。
摘要由CSDN通过智能技术生成

这篇为上篇的下半部分,也就是.cpp文件的解读。这个文件中主要是对类成员函数的一些定义。一些操作方法还是很常用的。

还是有没细化的地方,后续慢慢搞了。
贴代码:


#include "command_args.h"

#include <cstdlib>
#include <cstring>
#include <fstream>
#include <algorithm>
#include <functional>


using namespace std;


// forward declarations
//四个重载,用于输入输出int型数组和double型数组,这个纯粹就是功能,跟BA程序没关系。
std::istream& operator>>(std::istream& is, std::vector<int>& v);
std::ostream& operator<<(std::ostream& os, const std::vector<int>& v);
std::istream& operator>>(std::istream& is, std::vector<double>& v);
std::ostream& operator<<(std::ostream& os, const std::vector<double>& v);

/**
 * 重载输入符>>,让其能够给int类型数组进行输入。
 * 参数很明显就是两个了,输入流对象引用和要被输入的数组
 */
std::istream& operator>>(std::istream& is, std::vector<int>& v)
{
    //创建一个字符串s,用于承接输入流is中的数据
    string s;
    //如果输入失败,则直接return了。这里主要用处还是防呆。
    // 为什么这里可以用>>呢,因为输入流到字符串是已经定义好的,流中的数据本来就是一律视为字符的,所以直接输入到字符串是没毛病的。
    if (! (is >> s) )
        return is;

    /**
     * 如果能走到这里,说明没有在上一步return,说明is >> s成功了,说明输入流中的数据已经存进了字符串s中。
     * 那么下面就是将字符串s中的数字解析出来,存进int型数组v中。
     */

    //.c_str()函数返回字符串的首字符地址。返回类型为const char*,所以不能直接赋值给char*类型
    const char* c = s.c_str();
    //这一步用了一下类型转换,将const char*类型的c,转换成char*类型,并赋值给char*类型的caux。
    //最后要的结果就是: caux 指向了字符串s的首字符的地址。
    char* caux = const_cast<char*>(c);

    //首先将数组v清空。
    v.clear();
    //定义一个循环遍历控制标识符,bool型
    bool hasNextValue=true;
    //通过hasNextValue控制循环解析
    while(hasNextValue)
    {
        /**
         * string to long int 函数说明
         * 函数定义: long int strtol(const char *nptr, char **endptr, int base)
         * 函数说明: 将nptr字符串转换成长整型数
         * 参数说明:
         * const char *nptr,要被转换的字符串
         * char **endptr,转换到哪个字符不符合条件结束了,将指向这个字符的指针的地址反馈给endptr。这里发现endptr是char**类型的。
         * base,指转换成几进制的数。base范围从2至36,或0。
         * 如base值为10则采用10进制(字符串以10进制表示),若base值为16则采用16进制(字符串以16进制表示)。
         * 当base值为0时则是采用10进制做转换,但遇到如''0x''前置字符则会使用16进制做转换。
         */

        /**
         * 由这里发现门路了。指针c用于指向解析的开头处,指针caux用于指向解析的结尾处,
         */

        //这里开始解析转换:
        // 从c指向的地方开始解析,
        // 将一次解析结束的位置,赋值给caux,
        // 按照10进制解析
        // 解析出来的返回类型为long int,所以进行下类型转换,转换成int型,并赋值给i。
        // 这句完之后,一次解析就完成了,解析出来的数字存在了i中。c指向开头处,caux指向解析掉i后的位置。
        int i = static_cast<int>(strtol(c,&caux,10));

        //下面这里判断一下指向开头的c和指向结束的caux是否指向了同一个位置,如果指向同一个位置,说明已经解析完了。
        //因为如果解析完了的话,c开头解析,没解析就停止了,导致caux指向的就是c开头的位置,说明解析完了。

        //如果没解析完,进行一些正常的操作:
        if (c!=caux)
        {
            //将末尾指针caux的值赋值给开头指针c,
            c=caux;
            //跳出的位置肯定不是数字了,所以从下一个字符开始
            c++;
            //然后就是最重要的,将解析出来的数字,push进数组v中。
            v.push_back(i);
        }
        //else说明解析完了,将循环判断标志置为false,然后循环就跳出了。
        else hasNextValue = false;
    }
    //当然最后,为了能够链接式的使用>>,最后肯定要返回输入流对象引用,同理上面判断失败直接return,也是返回的is。
    return is;
}

/**
 * 重载输出符<<,让其能够对int类型数组进行输出。
 * @param os 输出流对象引用
 * @param v 要被输出的int数组
 * @return 因为要保证链式使用,所以返回输出流对象引用
 */
std::ostream& operator<<(std::ostream& os, const std::vector<int>& v){
    //将第一个元素单独输出,因为后面的循环看出来,元素前面有个"," 而第一个是不需要的,所以单独输出。
    //同时判断一下,v是否为空。
    if (v.size()){
        os << v[0];
    }
    //剩下的就是for循环进行输出了,从第二个元素开始,直到最后一个。
    for (size_t i=1; i<v.size(); i++){
        os << "," << v[i];
    }
    //最后返回输出流对象引用
    return os;
}

/**
 * convert a string into an other type.
 * 将字符串转化成其他类型,一般是纯数字字符串转化成int、double、float值等这种用法。明显要用模板。
 *
 * 原理还是利用字符串流,先构造字符串输入流i,再将其作为输入,输入到x中。
 * 返回值返回转化成功与否
 */
template<typename T>
bool convertString(const std::string& s, T& x)
{
    //这里用s字符串构造了一个输入流对象i,
    std::istringstream i(s);
    //将流输入进x,同时判断输入是否成功。
    if (! (i >> x))
        return false;
    return true;
}

/** Helper class to sort pair based on first elem */
//??啥玩意?干啥的?
template<class T1, class T2, class Pred = std::less<T1> >
struct CmpPairFirst {
    bool operator()(const std::pair<T1,T2>& left, const std::pair<T1,T2>& right) {
        return Pred()(left.first, right.first);
    }
};

//这里就是参数类型的枚举,用不同的int值来表征类型,也就是为什么commad_args中的type属性的类型是int型的了。
enum CommandArgumentType
{
    CAT_DOUBLE, CAT_FLOAT, CAT_INT, CAT_STRING, CAT_BOOL, CAT_VECTOR_INT, CAT_VECTOR_DOUBLE
};

//class CommandArgs的默认构造函数
CommandArgs::CommandArgs() {}

//class CommandArgs的默认析构函数
CommandArgs::~CommandArgs() {}

/**
 * 整个的命令行用法应该是:
 * ./progname -parameter1 parameter1_value -parameter2 parameter2_value -- parameterleftover...
 *
 * 整体的逻辑是程序名后面接一个-参数名,参数名后面接参数值,成对出现,在后面会有--或者直接是参数名的,这些是放到_leftOvers[]中的。
 * 对于-参数,就去_args[]数组中去寻找,找到后将自定义的值覆盖进去。没找到肯定报错。没找到说明输入了不相关的参数。
 * 对于_leftOvers[]中的参数,就依次读进去值,在_leftOvers[]读满了之后,将后面的放在_leftOversOptional[]中。
 */

bool CommandArgs::parseArgs(int argc, char** argv, bool exitOnError)
{
    //第一个命令行参数为程序名称,将其保存在_progName
    _progName = argv[0];

    int i;
    //用argc控制循环遍历命令行参数数组。
    for (i = 1; i < argc; i++)
    {
        //这里将第i个参数赋值给name字符串。
        string name = argv[i];

        //每个命令参数的参数名必须以至少一个‘-’字符开头,多个也没问题,不是的话直接break到后面对于_leftOvers[]的输入
        if (name[0] != '-') { // each param has to start with at least one dash
            //cerr << "Error: expecting parameter, got " << name << endl;
            //printHelp(cerr);
            //if (exitOnError)
            //exit(1);
            //return false;
            break;
        }
        /* first check whether it's -- and we should not continue parsing */
        //首先检查是否是'--',表征人为自定义值输入结束,也是break到后面的_leftOvers[]的输入
        if (name == "--") {
            //这里++i是因为本字符已经被检查出是"--"了,再读取就没什么意思了,所以从后一开始。
            //对比上面的break,上面的只是检测出来一个命令行参数不是以'-'开头,所以有可能就是_leftOvers[]的输入了,所以不++i
            ++i;
            break;
        }

        //上面过滤掉了没有'-'和‘--’的情况,那么剩下的就是‘-’和‘----’的情况,找到第一个不是'-'的地方,然后将前面的切掉。重新赋值name
        string::size_type dashPos = name.find_first_not_of('-');
        //判断一下,防止只有横杠的情况。
        if (dashPos != string::npos)
            //剪切掉dashPos之前的东西,只留后面的,重新赋值name,此时的name就是干净的参数名了。
            name = name.substr(dashPos);

        //判断剩下的name字符串是否是”help“或者”h“,是的话就打印帮助然后,exit()
        if (name == "help" || name == "h")
        {
            printHelp(cout);
            exit(0);
        }
        else {
            // command line argument parsing
            //排除掉上面一切其他情况,这里开始正常的参数赋值。
            //这里创建了一个迭代器:std::vector<CommandArgument>::iterator类型的it,指向_args的开始处。
            std::vector<CommandArgument>::iterator it = _args.begin();
            //迭代器开始在_args中找相应的参数。
            for ( ; it != _args.end(); ++it)
            {
                //如果_args数组中找到了命令行中对应的参数名
                if (it->name == name)
                {
                    //找到有此参数之后,看类型是否是bool型的,如果是,操作上就是直接翻转
                    if (it->type == CAT_BOOL)
                    {
                        //判断一下是否没有读取,没读取执行下面的操作:对于读取过了的情况发现就直接不管了。不进行任何操作。
                        if (!it->parsed)
                        {
                            //同样的套路,将参数取出进行赋值就好了,由于这里是bool类型,所以直接拉出来翻转即可。
                            bool* data = static_cast<bool*>(it->data);
                            *data = !(*data);
                        }
                        //操作完之后,将其parsed属性置为true。表征命令行读入参数值成功
                        it->parsed = true;
                    }
                    //else的情况,即为不是bool类型的参数。
                    else
                    {
                        /**
                         * 这一步的意义是检查一下命令行是否只给了参数名,而后面结束了,并没有给参数的值。
                         * 说一下原理:整个程序有两处对i的值进行操作,一处就是for循环中的i++,这里记为第一个i++
                         * 另外一处就是在str2arg(argv[i], *it);读入参数值之前的一句i++,这里记为第二个i++
                         * 也就是说,从第一个i++之后到第二个i++之前,argv[i]都是参数名。
                         * 第二个i++之后到第一个i++之前,argv[i]都是参数值。
                         * 很明显,这一句的位置在第一个i++与第二个i++之间。argv[i]应该是一个参数名。
                         * 而i >= argc-1是什么意思呢?
                         * 先看i = argc-1的情况:i在这里的作用就是用于argv[i],用于遍历命令行的各个命令的。
                         * 那么argv[argc-1]是哪个命令呢,由于argv的索引从0开始,而argc为个数计数,是从1开始的,所以argv[argc-1]就是只的是最后一个命令行参数
                         * 问题来了,此处的argv[i]是一个参数名,后面是需要有参数值的,而这里判断这个参数已经为最后一个命令了,后面没有命令提供参数值了。
                         * 所以面的一系列报警输出,exit()等就好理解了。
                         * 至于i>argc-1的情况就更好理解了。
                         * 举个例子,假如命令行就提供了一个命令参数,此时argc=2,argv也就只有argv[0]和argv[1],
                         * 你非得去读取argv[2]或者argv[3]等等越界的地方,肯定结果也是输出报错。
                         */
                        if(i >= argc-1)
                        {
                            cerr << "Argument " << name << "needs value.\n";
                            printHelp(cerr);
                            if (exitOnError)
                                exit(1);
                            return false;
                        }
                        //紧接着,跟在读参数名后面的参数值,所以i++,让argv[i]对应参数值。
                        i++;
                        // 由于命令行类型一律是字符串,所以用到了定义的字符串转参数的函数,
                        // 看函数定义发现其实就是用字符串格式的参数值给参数赋值。
                        str2arg(argv[i], *it);
                        //解析完成,将parsed置为true
                        it->parsed = true;
                    }
                    /**
                     * 这个break想了好久。。还是程序结构没看懂。
                     * for ( ; it != _args.end(); ++it){}中是在拿着命令行的参数名(这里的name)去_args[]中去寻找对应的参数,找到了将值更新。
                     * 这个for{}中只有一个if (it->name == name){},也就是说只定义了找到的情况,找到后自然就是赋值,parsed置为true等操作
                     * 问题就在这,赋值完了,parsed也置为true了之后呢,之后程序怎么走?
                     * 仔细观察你就发现会自然的会走到这一句break,跳出这个for ( ; it != _args.end(); ++it){}遍历_args[]的循环。
                     * 跳出之后, 如果下面的if (it == _args.end()){}不执行,那就正常跑到了for (i = 1; i < argc; i++){}的一次循环的末尾,
                     * 也就是// for argv[i]这一句的那个括号。去之后就是再读一个argv[i],再去遍历_args[],找到赋值,找不到报警。如此循环。
                     *
                     */
                    break;
                }
            }

            //如果it被++到了_args末尾后一个,说明命令行中输入的参数名,并没有在_args[]数组中找到,也就是说命令行中输入了一个不相干的参数名,
            //所以也是输出报警信息,并退出,返回false等一些列操作。
            /**
             * 这一句也是因上面的break才可以这么写。上面找到对应的变量并赋值成功后会break,说明it会在_args[]中间的一个位置。
             * 所以成功的话这个if永远不会成立。
             * 成立的情况是什么样的呢?it在遍历 _args[]时,遍历到末尾遍历完了也没找到。
             * 也就是说上方的for ( ; it != _args.end(); ++it){}都完整运行完了,也没找到。
             * 那就说明你命令行输入的参数名我这个程序并不认识。输入了一个不相干的参数。所以报警输出为unknown
             */
            if (it == _args.end())
            {
                cerr << "Error: Unknown Option '" << name << "' (use -help to get list of options).\n";
                if (exitOnError)
                    exit(1);
                return false;
            }

        }

    } // for argv[i]

    /**
     * 这里是承接上面判断命令行的参数开头没有-或者为--的。两个break到了这里。
     * ‘--’有i++,argv[i]跳到了下一个字符串,而没有-的也就没有i++,说明argv[i]就在brake处的当前字符串。
     * 这里的判断是说剩余的命令行参数够不够填满_leftOvers[]了,而这里面的参数必须要用户定义值,如果不够了,说明没有提供足够的必要参数值。
     * 所以这里就cerr报警说缺参数值,打印帮助,程序退出,返回false等一系列的操作。
     * 如果这一步不满足,说明剩下的命令行参数还够填满_leftOvers[]数组,填满了多的就继续放在_leftOversOptional[]中
     */
    if ((int)_leftOvers.size() > argc - i) {
        cerr << "Error: program requires parameters" << endl;
        printHelp(cerr);
        if (exitOnError)
            exit(1);
        return false;
    }

    //这里其他参数的命令行赋值,就必须要对其顺序了。一般都会先将必须要有的_leftOvers[]赋全,然后触发j < _leftOvers.size()不满足,循环结束
    //因为不检查名字,按顺序赋值给_leftOvers[j],所以顺序一定要对齐。
    //这里说一下这两个循环控制符i和j,由于上方的if已经判定了,所以一定是j < _leftOvers.size()先触发完毕,也就是赋全了_leftOvers[].
    for (size_t j = 0; (i < argc && j < _leftOvers.size()); i++, j++) {

        //这两步捯饬还是将命令行中的值,赋值给内部的参数。
        // 一步一步来看,_leftOvers[j].data是描述块所描述的参数变量的地址,将这个地址赋值给s
        string* s = static_cast<string*>(_leftOvers[j].data);
        //将s所指地址上的数据,赋值为argv[i],s就是指的描述块所描述的参数变量。所以参数变量的值被修改
        *s = argv[i];
        //这里来看,还是一步比较容易理解:将.data所指的地方的值,更新为argv[i]
        //*(static_cast<string*>(_leftOvers[j].data))=argv[i];
    }

    // the optional leftOvers
    //然后就是这里了,将填满_leftOvers后剩下的命令行参数值放到_leftOversOptional中。由于不检查名字,也是按顺序直接赋值,所以顺序一定要对齐。
    //这里的i,j判定条件谁先达到就不知道了。i先到头,说明_leftOversOptional[]没有赋全,可选,没关系。
    // j先到头,说明_leftOversOptional[]也赋全了,所有的参数都已经被自定义了值,后面剩下的那些命令行参数就扔了,不要了。
    for (size_t j = 0; (i < argc && j < _leftOversOptional.size()); i++, j++) {
        string* s = static_cast<string*>(_leftOversOptional[j].data);
        *s  = argv[i];
    }

    //能走到这一步,说明正确解析完成,没有在中间报错退出,所以可以返回true了。
    return true;
}

/**
 * 下面一堆重载的param()函数,用于往_args[]数组中添加参数描述块。
 * @param name 参数名称
 * @param p 参数快链接的参数。这个就是BA要用的内部参数。
 * @param defValue 参数值
 * @param desc 参数描述
 */
void CommandArgs::param(const std::string& name, bool& p, bool defValue, const std::string& desc)
{
    CommandArgument ca;
    ca.name = name;
    ca.description = desc;
    ca.type = CAT_BOOL;
    //将字符串引用的地址传到.data中去,也就是这里存储数据是存储的数据指针
    ca.data = static_cast<void*>(&p);
    ca.parsed = false;
    //这一步其实是跟ca没啥关系的,只是将p的值赋值为传入的值。
    p = defValue;
    //将ca压入_args数组中。
    _args.push_back(ca);
}

/**
 *
 * 这里继续总结一下class CommandArgs类中的struct CommandArgument结构体
 * 这个结构体是_args数组中的基本元素类型,添加元素的话只能通过调用param()函数,因为只有这个函数中有_args.push_back()操作。
 * 再来观察一下结构体的各个元素: .name .description .type .parsed 这些都好理解,都是参数的一些描述,
 * 来看最核心的.data。这个成员的类型是void*类型的,也就是个指针,这个指针指向哪里呢?指向param()函数中的第二个参数,也就是int& p这个。
 * 这里也就发现眉目了。在_args[]数组中存的各个ca,其实并不是参数本身(至少在ca个各个属性中,没有参数值一说,全是参数的描述),而是一个具体参数后面的一些描述(name,type...),
 * 通过.data指向具体的参数,也就是说ca不能单独存在,必须有外部真实的参数变量进行挂靠。
 * 这点从param()中必须要传入一个int& p实打实的int类型引用 和 BundleParams.h中的struct BundleParams结构体中定义的那一堆参数变量可见
 * 终其原因就是_args[]数组中的CommandArgument类型的元素并不会真正的存储参数和参数的值,它只是通过一个.data指针指向一个外部存在的参数。
 * 跟这个参数对应之后,CommandArgument只是用来存储这个参数的一些信息。
 * 至于为什么这么搞,猜测还是为了-help。
 *
 *
 */

void CommandArgs::param(const std::string& name, int& p, int defValue, const std::string& desc)
{
    CommandArgument ca;
    ca.name = name;
    ca.description = desc;
    ca.type = CAT_INT;
    ca.data = static_cast<void*>(&p);
    ca.parsed = false;
    p = defValue;
    _args.push_back(ca);
}

void CommandArgs::param(const std::string& name, float& p, float defValue, const std::string& desc)
{
    CommandArgument ca;
    ca.name = name;
    ca.description = desc;
    ca.type = CAT_FLOAT;
    ca.data = static_cast<void*>(&p);
    ca.parsed = false;
    p = defValue;
    _args.push_back(ca);
}

void CommandArgs::param(const std::string& name, double& p, double defValue, const std::string& desc)
{
    CommandArgument ca;
    ca.name = name;
    ca.description = desc;
    ca.type = CAT_DOUBLE;
    ca.data = static_cast<void*>(&p);
    ca.parsed = false;
    p = defValue;
    _args.push_back(ca);
}

void CommandArgs::param(const std::string& name, std::string& p, const std::string& defValue, const std::string& desc)
{
    CommandArgument ca;
    ca.name = name;
    ca.description = desc;
    ca.type = CAT_STRING;
    //将字符串引用的地址传到.data中去,也就是这里存储数据是存储的数据指针
    ca.data = static_cast<void*>(&p);
    ca.parsed = false;
    //这一步其实是跟ca没啥关系的,只是将p的值赋值为传入的值。
    p = defValue;
    //将ca压入_args数组中。
    _args.push_back(ca);
}

void CommandArgs::param(const std::string& name, std::vector<int>& p, const std::vector<int>& defValue, const std::string& desc){
    CommandArgument ca;
    ca.name = name;
    ca.description = desc;
    ca.type = CAT_VECTOR_INT;
    ca.data = static_cast<void*>(&p);
    ca.parsed = false;
    p = defValue;
    _args.push_back(ca);
}

void CommandArgs::param(const std::string& name, std::vector<double>& p, const std::vector<double>& defValue, const std::string& desc)
{
    CommandArgument ca;
    ca.name = name;
    ca.description = desc;
    ca.type = CAT_VECTOR_DOUBLE;
    ca.data = static_cast<void*>(&p);
    ca.parsed = false;
    p = defValue;
    _args.push_back(ca);
}

//这里这个打印help函数瞅着就蛋疼。。。不看了
void CommandArgs::printHelp(std::ostream& os)
{
    if (_banner.size())
        os << _banner << endl;
    os << "Usage: " << _progName << (_args.size()>0?" [options] ":" ");
    if (_leftOvers.size() > 0) {
        for (size_t i = 0; i < _leftOvers.size(); ++i) {
            if (i > 0)
                os << " ";
            os << _leftOvers[i].name;
        }
    }
    if (_leftOversOptional.size() > 0) {
        if (_leftOvers.size() > 0)
            os << " ";
        for (size_t i = 0; i < _leftOversOptional.size(); ++i) {
            if (i > 0)
                os << " ";
            os << "[" << _leftOversOptional[i].name << "]";
        }
    }
    os << endl << endl;
    os << "General options:" << endl;
    os << "-------------------------------------------" << endl;
    os << "-help / -h           Displays this help." << endl << endl;
    if (_args.size() > 0) {
        os << "Program Options:" << endl;
        os << "-------------------------------------------" << endl;
        // build up option string to print as table
        vector<pair<string, string> > tableStrings;
        tableStrings.reserve(_args.size());
        size_t maxArgLen = 0;
        for (size_t i = 0; i < _args.size(); ++i) {
            if (_args[i].type != CAT_BOOL) {
                string defaultValueStr = arg2str(_args[i]);
                if (! defaultValueStr.empty())
                    tableStrings.push_back(make_pair(_args[i].name + " " + type2str(_args[i].type), _args[i].description + " (default: " + defaultValueStr + ")"));
                else
                    tableStrings.push_back(make_pair(_args[i].name + " " + type2str(_args[i].type), _args[i].description));
            } else
                tableStrings.push_back(make_pair(_args[i].name, _args[i].description));
            maxArgLen = (std::max)(maxArgLen, tableStrings.back().first.size());
        }
        sort(tableStrings.begin(), tableStrings.end(), CmpPairFirst<string,string>());
        maxArgLen += 3;
        for (size_t i = 0; i < tableStrings.size(); ++i) {
            os << "-" << tableStrings[i].first;
            for (size_t l = tableStrings[i].first.size(); l < maxArgLen; ++l)
                os << " ";
            os << tableStrings[i].second << endl;
        }
        // TODO should output description for leftOver params?
    }
}

//设置banner,直接赋值。
void CommandArgs::setBanner(const std::string& banner)
{
    _banner = banner;
}

//这个是用于设定其他参数的函数。并根据是否可选,分别存进_leftOversOptional和_leftOvers中
void CommandArgs::paramLeftOver(const std::string& name, std::string& p, const std::string& defValue, const std::string& desc, bool optional)
{
    CommandArgument ca;
    ca.name = name;
    ca.description = desc;
    ca.type = CAT_STRING;
    ca.data = static_cast<void*>(&p);
    ca.parsed = false;
    ca.optional = optional;
    p = defValue;
    //这里根据是否可选分别存进不同的数组。
    if (optional)
        _leftOversOptional.push_back(ca);
    else
        _leftOvers.push_back(ca);
}

//将类型转换成字符串,貌似是用于类型的输出?程序很简单,switch分列就好了。返回字符串。
const char* CommandArgs::type2str(int t) const
{
    switch (t) {
        case CAT_DOUBLE:
            return "<double>";
        case CAT_FLOAT:
            return "<float>";
        case CAT_INT:
            return "<int>";
        case CAT_STRING:
            return "<string>";
        case CAT_BOOL:
            return "<bool>";
        case CAT_VECTOR_INT:
            return "<vector_int>";
        case CAT_VECTOR_DOUBLE:
            return "<vector_double>";
    }
    return "";
}

/**
 * 字符串转参数。string to argument,
 * 其实其实功能并不是名字所表示的那样,功能就是将字符串类型的参数值,赋值给.data所指的参数。
 * @param input 输入的字符串
 * @param ca 要赋值的参数。 CommandArgument& 类型的引用。
 */
void CommandArgs::str2arg(const std::string& input, CommandArgument& ca) const
{
    //这里要根据ca的类型进行转化,用switch进行分列。
    switch (ca.type) {
        //以folat为例进行说明
        //流程就是将string转成float然后赋值给.data所指的参数。
        case CAT_FLOAT:
        {
            //定义一个承接float参数的变量aux
            float aux;
            //这个函数在上方定义了。将input字符串转化成float类型的aux,返回是否转化成功。
            bool convertStatus = convertString(input, aux);
            //如果成功,将ca.data指针转换成float*类型,并赋值为aux。
            if (convertStatus) {
                //其实可以这样一句:
                //这里注意一下,.data是的类型这里是void*,使用时要转换成其他基本类型指针。
                //可以发现还是赋值,其他并没有什么操作。
                *(static_cast<float*>(ca.data)) = aux;
                //*(ca.data)=aux;这样是不行的,必须显式的表明类型转换。
                //float* data = static_cast<float*>(ca.data);
                //*data = aux;
            }
        }
            break;
            //同理
        case CAT_DOUBLE:
        {
            double aux;
            bool convertStatus = convertString(input, aux);
            if (convertStatus) {
                double* data = static_cast<double*>(ca.data);
                *data = aux;
            }
        }
            break;
        case CAT_INT:
        {
            int aux;
            bool convertStatus = convertString(input, aux);
            if (convertStatus) {
                int* data = static_cast<int*>(ca.data);
                *data = aux;
            }
        }
            break;
        case CAT_BOOL:
        {
            bool aux;
            bool convertStatus = convertString(input, aux);
            if (convertStatus) {
                bool* data = static_cast<bool*>(ca.data);
                *data = aux;
            }
        }
            break;
        case CAT_STRING:
        {
            //string这里就比较简单了,直接赋值就好了,不用转换。
            string* data = static_cast<string*>(ca.data);
            *data = input;
        }
            break;
        case CAT_VECTOR_INT:
        {
            //同理,这里都是一眼的,先将字符串转化成int类型数组。
            std::vector<int> aux;
            bool convertStatus = convertString(input, aux);
            //转化成功后进行参数赋值。
            if (convertStatus) {
                std::vector<int>* data = static_cast< std::vector<int>* >(ca.data);
                *data = aux;
            }
        }
            break;
        case CAT_VECTOR_DOUBLE:
        {
            std::vector<double> aux;
            bool convertStatus = convertString(input, aux);
            if (convertStatus) {
                std::vector<double>* data = static_cast< std::vector<double>* >(ca.data);
                *data = aux;
            }
        }
            break;
    }
}

/**
 * 将参数值转化成字符串。同样也是用switch进行分列。
 * @param ca 需要转化哪个参数的值
 * @return 返回当然是字符串了。
 */
std::string CommandArgs::arg2str(const CommandArgument& ca) const
{
    switch (ca.type) {
        //以float类型参数为例。这里涉及到字符串流sstream的用法,字符串流长用于类型转化,参看:
        //http://blog.csdn.net/xiaogugood/article/details/21447431
        case CAT_FLOAT:
        {
            //将ca.data取出,赋值给data,后面都是操作这个data。
            float* data = static_cast<float*>(ca.data);
            //创建一个字符串流对象
            stringstream auxStream;
            //将data所指的参数值传入到字符串流中,
            // 细聊一句,为什么这里是参数值,因为如果data指向一个int,那么*data就是个int值。所以<<输入就是int值了。
            auxStream << *data;
            //用.str()方法返回字符串流中的字符,同时return。
            return auxStream.str();
        }
        case CAT_DOUBLE:
        {
            double* data = static_cast<double*>(ca.data);
            stringstream auxStream;
            auxStream << *data;
            return auxStream.str();
        }
        case CAT_INT:
        {
            int* data = static_cast<int*>(ca.data);
            stringstream auxStream;
            auxStream << *data;
            return auxStream.str();
        }
        case CAT_BOOL:
        {
            bool* data = static_cast<bool*>(ca.data);
            stringstream auxStream;
            auxStream << *data;
            return auxStream.str();
        }
        case CAT_STRING:
        {
            string* data = static_cast<string*>(ca.data);
            return *data;
        }
        case CAT_VECTOR_INT:
        {
            std::vector<int> * data = static_cast< std::vector<int> * >(ca.data);
            stringstream auxStream;
            auxStream << (*data);
            return auxStream.str();
        }
        case CAT_VECTOR_DOUBLE:
        {
            std::vector<double> * data = static_cast< std::vector<double> * >(ca.data);
            stringstream auxStream;
            auxStream << (*data);
            return auxStream.str();
        }
    }
    return "";
}

//从这个定义中看,貌似是将字符串中的纯字符提取出来,把前后的空格,缩进,换行都给剔除掉。
std::string CommandArgs::trim(const std::string& s) const
{
    //长度为0,说明是空字符串,直接return s
    if(s.length() == 0)
        return s;
    //find_first_not_of 从前往后查找,查找到不是子串中的任意一个字符,返回第一个的位置。这里相当于把前面的空格缩进和换行全部跳过。
    string::size_type b = s.find_first_not_of(" \t\n");
    //find_last_not_of 从后往前查找,查找到不是字串中的任意一个字符,返回最后一个位置。这里相当于把后面的空格缩进换行全都跳过。
    // 注意!从功能上看,这里并不能跳过字符串中间的空格缩进和换行,因为只是跳过了最前面的和最后面的。
    string::size_type e = s.find_last_not_of(" \t\n");

    //如果b的值为npos,npos是啥百度很多。这个判断的意思就是如果
    //上一句是找s中第一个不是空格缩进换行的字符位置。假如结果没找到的话,就会返回string::npos。
    //没找到说明什么意思呢?上面已经判定了字符串s不是空,但是又找不到不是空格缩进换行的字符,
    // 那就说明s是仅由空格缩进换行构成。所以后面只能返回空字符串""
    if(b == string::npos)
        return "";

    //排除了本身为空和全是空格缩进换行两种情况,剩下的肯定就是比较正常的了。掐头去尾留中间,构造一个string,然后return
    //这里说一下b ,e 这俩都是位置,也就是偏移量,起点都是字符串的首字符。
    //这里用到了string类的构造函数:String(char[] str,int index,int length)
    //在str字符串中的index位置开始,往后取一个length长度的字符串。
    //这样也就明白了第三个参数为什么是e-b+1了。其实是e-(b-1),从e位置,减掉b前一个位置,最后就是从b到e的长度了。
    return std::string(s, b, e - b + 1);
}

/**
 * 这个double类型数组输入,套路跟前面的int类型的一样。唯一不同的是strtod(c,&caux)这个函数的调用。
 * string to double函数,用法原理一样,就是没有了后面的进制参数。所以这里不细说了。
 * @param is
 * @param v
 * @return
 */
std::istream& operator>>(std::istream& is, std::vector<double>& v){
    string s;
    if (! (is >> s) )
        return is;

    const char* c = s.c_str();
    char* caux = const_cast<char*>(c);

    v.clear();
    bool hasNextValue=true;
    while(hasNextValue){
        double i=strtod(c,&caux);
        if (c!=caux){
            c=caux;
            c++;
            v.push_back(i);
        } else
            hasNextValue = false;
    }
    return is;
}

/**
 * double类型数组输出也是一样,参看上方int类型。
 * @param os
 * @param v
 * @return
 */
std::ostream& operator<<(std::ostream& os, const std::vector<double>& v)
{
    if (v.size())
        os << v[0];
    for (size_t i=1; i<v.size(); i++)
        os << ";" << v[i];
    return os;
}

//查看参数的parsed属性。看看是否由命令行正确读入了进来。也就是一个参数是否被用户自定了。
bool CommandArgs::parsedParam(const std::string& param) const
{
    //std::vector<CommandArgument>::const_iterator it = _args.begin();
    auto it = _args.begin();
    //遍历参数数组
    for ( ; it != _args.end(); ++it)
    {
        //如果找到名称一样的,直接return返回它的parsed属性。
        if (it->name == param) {
            return it->parsed;
        }
    }
    //如果遍历完了都没有,则返回false,说明参数列表中压根没找到这个参数,也是返回false
    return false;
}

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值