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 << "\t" << 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 == '\t')
    {
        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文件的讀取工作了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM