Простой способ читать файлы конфигурации TXT на C ++ - PullRequest
0 голосов
/ 10 июля 2020

этот вопрос вроде бы уже задан, но я не нашел удобного решения для своего случая. У меня есть следующий файл конфигурации TXT для чтения на C ++:

--CONFIGURATION 1 BEGIN--

IP address:                          192.168.1.145
Total track length [m]:              1000
Output rate [1/s]:                   10
Time [s]:                            1
Running mode (0=OFF 1=ON):           1
Total number of attempts:            10
Mode (0=OFF, 1=BEG, 2=ADV, 3=PROF):  1

--Available only for Administrators--

Variable 1 [mV]:                     2600
Gain 1 [mV]:                         200
Position tracking (0=OFF 1=ON):      0
Coefficient 2 [V]:                   5.2

--CONFIGURATION 1 END--

--CONFIGURATION 2 BEGIN--

Max track distance [m]:             10000
Internal track length [m]:          100
Offset distance [mV]:               1180
GAIN bias [mV]:                     200
Number of track samples:            1000
Resolution (1 or 2) [profile]:      1

--CONFIGURATION 2 END--

Мне нужно сохранить только значение в конце каждой строки, которое может быть строкой (в случае IP-адреса), int, float или bool внутри структуры. В C есть очень простое решение, я читаю каждую строку, используя следующее выражение:

if(!fscanf(fp, "%*s %*s %*s %*s %d\n", &(settings->trackLength))) {

    printf("Invalid formatting of configuration file. Check trackLength.\n");
    return -1;
}

% * s позволяет отбросить метку строки и пробелы перед интересующим значением . Я использую fgets, чтобы пропустить пустые строки или заголовки. Этот способ работает и в C ++. Хорошо ли оставить мой код как есть или вы видите лучший и простой способ сделать это на C ++? Большое спасибо.

1 Ответ

1 голос
/ 10 июля 2020

Также в C ++ легко разделить строку. Я уже дал здесь несколько ответов о том, как разбить строку. В любом случае, я объясню это здесь подробно и для вашего особого случая. Позже я также предоставлю полный рабочий пример.

Мы используем базовую c функциональность std::getline, которая может читать всю строку или строку до заданного символа. См. здесь .

Давайте рассмотрим пример. Если текст хранится в std::string, мы сначала поместим его в std::istringstream. Затем мы можем использовать std::getline для извлечения данных из std::istringstream. Это всегда стандартный подход. Сначала прочтите всю строку из файла, используя std::getline, затем снова поместите ее в std::istringstream, чтобы снова можно было извлечь части строки с помощью std::getline.

Если исходная строка выглядит так:

Time [s]:                            1

Мы можем заметить, что у нас есть несколько частей:

  • идентификатор «Time [s]»,
  • двоеточие, которое действует как разделитель,
  • один или несколько пробелов и
  • значение «1»

Итак, мы могли бы написать что-то вроде этого:

std::string line{};  // Here we will store a complete line read from the source file
std::getline(configFileStream, line);  // Read a complete line from the source file
std::istringstream iss{ line };  // Put line into a istringstream for further extraction

std::string id{};  // Here we will store the target value "id"
std::string value{};   // Here we will store the target "value"
std::getline(iss, id, ':');  // Read the ID, get read of the colon
iss >> std::ws;  // Skip all white spaces
std::getline(iss, value);  // Finally read the value

Итак, это много текста. Возможно, вы слышали, что можно связать операции ввода-вывода, как в std::cout << a << b << c. Это работает, потому что операция << всегда возвращает ссылку на данный поток. То же самое и с <code>std::getline. И поскольку он это делает, мы можем использовать вложенные операторы. Это означает, что мы можем поместить второй std::getline в эту позицию параметра (фактически первый параметр), где он ожидает std::istream. Следовательно, если мы будем следовать этому подходу, мы можем написать вложенный оператор:

std::getline(std::getline(iss, id, ':') >> std::ws, value);

Ой, что здесь происходит? Давайте проанализируем изнутри. Сначала операция std::getline(iss, id, ':') извлекает строку из std::istringstream и присваивает ее переменной «id». Хорошо понял. Помните: std :: getline вернет ссылку на данный поток. Итак, приведенный выше сокращенный оператор:

std::getline(iss >> std::ws, value)

Далее, iss >> std::ws будет вычислен и приведет к поглощению всех ненужных пробелов. И угадайте, что он вернет ссылку на поток gievn "iss".

Теперь оператор выглядит так:

std::getline(iss, value)

И это будет читать значение. Просто.

Но мы еще не закончили. Конечно, std :: getline снова вернет "iss". И в приведенном ниже коде вы увидите что-то вроде

if (std::getline(std::getline(iss, id, ':') >> std::ws, value))

, что в итоге будет if (iss). Итак, мы используем iss как логическое выражение? Почему это работает и для чего это нужно? Это работает, потому что bool operator из std::stream перезаписывается и возвращается, если состояние в порядке или имеет сбой. См. здесь для объяснения. Всегда проверяйте результат любой операции ввода-вывода.

И последнее, но не менее важное: нам нужно объяснить оператор if с инициализатором. Вы можете прочитать об этом здесь .

Я могу написать

if (std::string id{}, value{}; std::getline(std::getline(iss, id, ':') >> std::ws, value)) {

, что похоже на

std::string id{}, value{}; 
if (std::getline(std::getline(iss, id, ':') >> std::ws, value)) {

Но в первом примере преимущество в том, что определенные переменные будут видны только в пределах if -statements. Итак, мы «ограничиваем» переменную как можно более узкой.

Вам следует стараться делать это как можно чаще. Вы также должны всегда проверять состояние возврата операции ввода-вывода, применяя if к операции с потоком, как показано выше.

Полная программа для чтения всего будет состоять из нескольких строк кода.

#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
#include <unordered_map>
#include <iomanip>

int main() {

    // Open config file and check, if it coul be opened
    if (std::ifstream configFileStream{ "r:\\config.txt" }; configFileStream) {

        // Here we wills tore the resulting config data
        std::unordered_map<std::string, std::string> configData;

        // Read all lines of the source file
        for (std::string line{}; std::getline(configFileStream, line); )
        {
            // If the line contains a colon, we treat it as valid data
            if (if (line.find(':') != std::string::npos)) {

                // Split data in line into an id and a value part and save it
                std::istringstream iss{ line };
                if (std::string id{}, value{}; std::getline(std::getline(iss, id, ':') >> std::ws, value)) {

                    // Add config data to our map
                    configData[id] = value;
                }
            }
        }
        // Some debug output
        for (const auto& [id, value] : configData)
            std::cout << "ID: " << std::left << std::setw(35) << id << " Value: " << value << '\n';
    }
    else std::cerr << "\n*** Error: Could not open config file for reading\n";

    return 0;
}

В этом примере я сохраняю идентификаторы и значения на карте, чтобы к ним можно было легко получить доступ.

...