• STL简洁 && c++读取cfg文件


      在c++工程中,往往需要修改一些变量来实现不同的功能效果,这是cfg文件的使用可以使得工程更加高效与便利,这篇文章介绍的就是c++读取cfg文件的相关内容,以便及时总结和日后回顾。

      STL即标准模板库,其包含了很多基本的数据结构和基本算法,实现了软件的高复用性,在标准中,STL包含13个头文件,包括<map>、<set>、<queue>等。 

      所有的容器都具有一些成员函数,包括默认构造函数、复制构造函数、析构函数、empty(判断容器是否为空)、max_size(返回容器最大容量)、size(返回容器当前的数量)、swap(交换两个容器中的元素。且很多容器还具有begin、end、rbegin、rend、erase、clear等方法来迅速获取到其中的某一个元素。这里我们主要介绍map容器。

      map是关联容器,是映射类的模板,下面的例子是演示map的用法,如下:

    #include <iostream>
    #include <string>
    #include <map>
    using namespace std;
    int main()
    {
        string name[] = {"wanye", "hedy", "jack"};
        double salary[] = {20000, 8000, 1200};
        map<string, double> sal;
        map<string, double>::iterator p; // 建立映射遍历器
        for (int i = 0; i < 3; i++) // 将name和salary映射到map中
            sal.insert(make_pair(name[i],salary[i]));
        sal["bob"] = 6000;
        sal["mark"] = 7000;
        for ( p = sal.begin(); p != sal.end(); p++)
            cout << p->first << "	" << p->second << endl;
        cout << "输入需要查找人的姓名:" << endl;
        string foo;
        cin >> foo;
        bool b = false;
        for (p = sal.begin(); p != sal.end(); p++)
        {
            if (p->first == foo)
            {
                cout << p->second << endl;
                b = true;
            }
        }
        if (b == false)
        {
            cout << "不存在该员工!" << endl;
        }
        return 0;
    }

    以上程序就是将员工姓名和工资存入map中,并根据map的key来查找map的value。需要注意的点有以下几个:

    • 可以看出map实际上是一个模板类,我们使用语句map<string, double> sal;实际上就是创建了一个map对象sal,这个sal对象本身具有一些方法,如begin、end、insert等。 
    • 引入string库可以建立string数组。 
    • 因为要遍历map结构,所以需要建立遍历器iterator。
    • 使用insert方法插入时,格式为map.insert(make_pair(foo, bar));
    • map具有begin和end两个方法,两者的返回值并不是一个简单的数字,而是遍历器iterator值,通过iterator的first和second属性可以分别访问到map的key和value。
    • map的添加除了使用insert方法外,还可以使用 下标法 插入。 
    • 在map表中查询时,需要将整个map结构进行遍历。

    介绍了map之后,我们就可以试着读取cfg文件中的项目配置信息了。

    首先,我们要知道cfg文件的格式,右边都是 key = value 这种格式,并且,我么可以使用 # 作为注释的标记符号。如下

    #this is a comment
    a = 100
    b = 100 #this comment is valid
    
    # this is another
    c = 200

    上面就是注释的两种使用,一种单独占用一行,另外一种在K-V之后添加。

    这一部分,我们也可以写成一个c++工程,那么一般就要有.h和.cpp文件,在主程序cpp文件中,是逻辑程序,所以,一些方法的定义最好定义在其他文件中,一般,我们定义在.h文件中,然后在对应的cpp文件中补充,这样,在主程序中,我们就可以引入.h文件来简化程序,使得程序的表达更为清晰、高效,如下,我们建立get_config.h文件:

    #ifndef __GET_CONFIG_H__
    #define __GET_CONFIG_H__
    
    #include <string>
    #include <map>
    #include <iostream>
    using namespace std;
    
    #define COMMENT_CHAR '#'
    
    bool ReadConfig(const string & filename, map <string, string> & m);
    void PrintConfig(const map<string, string> & m);
    void FindInConfig(map<string, string> m, string key);
    #endif 

    这里 #define COMMENT '#'是宏定义的方式,易于扩展和重构,增加程序的效率,下面的两句定义了两个函数,一个是读取配置信息的函数,另外一个是打印所有配置信息的函数,实参中的 & 是引用,这样就不是简单的赋值,而是修改实参同时也在修改传递进来的值。

    • 我们在这里引入了 string、map、iostream,这样,在main.cpp中我们就不需要重复引入这些了。
    • 使用COMMENT_CHAR更为清晰易懂一些。
    • 这里定义了一些主要的函数,是为了在main.cpp使用,而get_config.cpp中的其他函数都是实现,而非接口。

    下面是get_config.cpp文件的代码:

    #include "get_config.h"
    
    #include <fstream>
    #include <iostream>
    
    using namespace std;
    
    bool IsSpace(char c)
    {
        if (c == ' ' || c == '	')
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    
    bool IsCommentChar(char c)
    {
        if (c == COMMENT_CHAR)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    
    // trim函数的作用是把一个字符串左边和右边的空格去掉,即为trim
    void Trim(string & str) // 引用传参,这样在函数中修改该参数也会修改相应的变量
    {
        if (str.empty())
        {
            return;
        }
        int i, start_pos, end_pos;
        for (i = 0; i < str.size(); i++)
        {
            if (!IsSpace(str[i]))
            {
                break;
            }
        }
        if (i == str.size())
        {
            str = "";    
            return;
        }
        start_pos = i; // 获取到非空格的初始位置
    
        for (i = str.size() - 1; i >= 0; i--)
        {
            if (!IsSpace(str[i]))
            {
                break;
            }
        }
        end_pos = i;
        str = str.substr(start_pos, end_pos - start_pos + 1);
    }
    
    bool AnalyseLine(const string & line, string & key, string & value) // 分析一行,如果是注释行,则不处理,如果是k-v行,则提取出key-value值。
    {
        if (line.empty())
        {
            return false;
        }
        int start_pos = 0, end_pos = line.size() - 1, pos;
        if ((pos = line.find(COMMENT_CHAR)) != -1)
        {
            if (0 == pos)
            {
                return false; // 如果一行的开头是#,说明是注释,则 不需要
            }
            end_pos = pos - 1; // 可能是注释在k-v后的情况
        }
        string new_line = line.substr(start_pos, end_pos - start_pos + 1); // 删掉后半部分的注释 FIX_ME: 这里应该是减错了吧
        // 下面pos的赋值时必要的,这样,就可在后面得到Key和value值了。
        if ((pos = new_line.find("=")) == -1) //说明前面没有 = 号
        {
            return false;
        }
        key = new_line.substr(0, pos); // 获得key
        value = new_line.substr(pos + 1, end_pos + 1 - (pos + 1)); // 获得value
        Trim(key);
        if (key.empty())
        {
            return false;
        }
        Trim(value); // 因为这里的key和value都是引用传递,可以直接被修改,所以不用返回
        return true;
    }
    
    
    
    // 读取一个cfg文件并保存到map中,map中的key和value都是string类型的。
    bool ReadConfig(const string & filename, map<string, string> & m)
    {
        m.clear(); // 删除map容器中的所有k-v对
        ifstream infile(filename.c_str());
        if (!infile)
        {
            cout << "file open failed!" << endl; // 文件打开失败,返回错误信息。
            return false;
        }
        string line, key, value; // 为了后续保存kv对
        while (getline(infile, line))
        {
            if (AnalyseLine(line, key, value))
            {
                m[key] = value; // 保存到map容器中的方法。
            }
        }
        infile.close(); // 当读取完之后,就要关闭这个文件。
        return true;
    }
    
    void PrintConfig(const map<string, string> & m)
    {
        map<string, string>::const_iterator mite;
        for (mite = m.begin(); mite != m.end(); mite++)
        {
            cout << mite->first << "=" << mite->second << endl;
        }
    }
    
    void FindInConfig( map<string, string>  m, string  key) // 注意:之前用的一直都是string类型,所以这里用的也是string key,而不是char key。
    {
        map<string, string>::iterator it;
        it = m.find(key);
        if (it == m.end()) 
        {
            cout << "there is no " << key << endl;
        }
        else
        {
            cout << it->second << endl;
        }
    }
    • 这里通过map实现了cfg文件中kv对的存取,方便操作。
    • 注意这里的文件读取的操作,已经getline等相关函数。
    • 注意整体的逻辑: 打开文件、从上到下获取行、注释忽略、建立map、存取kv对,这样,我们就可以得到cfg文件中所有有用的参数了。 
    • 最后FindInConfig函数中,我们将找到的kv输出,当然,在使用过程中,我们可以直接获取进行进一步的计算工作。

    如下是main.cpp中的代码:

    #include "get_config.h"
    int main()
    {
        map<string, string> m;
        ReadConfig("C:\Users\Administrator\Desktop\readConfig\readConfig\config.cfg", m);
        PrintConfig(m);
        string key;
        cout << "Please input a key to find the value in config.cfg" << endl;
        cin >> key;
        FindInConfig(m, key);
        return 0;
    }
    • 这里使用了绝对位置。
    • main.cpp中只需引入get_config.h文件,而main.cpp就相当于入口文件。  

    我们通过运行,可以得到下面的运行结果:

    如上所示。

    通过上上面的小工程,我们就可以完成对cfg文件的读取工作了。

  • 相关阅读:
    Features for Multi-Target Multi-Camera Tracking and Re-identification论文解读
    CBAM(Convolutional Block Attention Module)使用指南
    j2ee web项目 ssh 中使用junit测试
    log4j 发送日志到邮箱
    java.util.ConcurrentModificationException
    java 项目 报错
    json 传参数到action中 乱码
    TOMCAT 信息
    action 纯注解 笔记
    java 上传图片 打水印
  • 原文地址:https://www.cnblogs.com/zhuzhenwei918/p/8569160.html
Copyright © 2020-2023  润新知