如何从C++中字段由'|'字符分隔的行中提取数据?

How to extract data from a line which has fields separated by '|' character in C++?

本文关键字:分隔 提取 数据 字符 字段 C++      更新时间:2023-10-16

我在文本文件中有以下格式的数据。文件名- empdata.txt注意行与行之间没有空格。

Sl | EmployeeID | | |部门乐队名字|位置

1|327427|Brock Mcneil|Research and Development|U2|Pune

2 | 310456 |阿克顿黄金广告| | P3 |海德拉巴

3 | 305540 | Hollee卡马乔工资| | U3 |班加罗尔

4|218801|Simone Myers|Public Relations|U3|Pune

5 | 144051 |伊顿本森|广告| P1 |钦奈

我有一个这样的类

class empdata
{
public:
int sl,empNO;
char name[20],department[20],band[3],location[20];
};

我创建了一个类empdata的对象数组。如何从上述指定格式的n行数据文件中读取数据,并将其存储到创建的(类)对象数组中?

这是我的代码

int main () {
string line;
ifstream myfile ("empdata.txt");
for(int i=0;i<10;i++) //processing only first 10 lines of the file
{
    getline (myfile,line);
    //What should I do with this "line" so that I can extract data 
    //from this line and store it in the class object?             
     
}
  return 0;
}

所以基本上我的问题是如何从一个字符串中提取数据,该字符串的数据由'|'字符分隔,并将每个数据存储到一个单独的变量

我更喜欢使用字符串工具包。String Toolkit在解析时将负责转换数字。

我是这样解决的。

#include <fstream>
#include <strtk.hpp>   // http://www.partow.net/programming/strtk
using namespace std;
// using strings instead of character arrays
class Employee
{
    public:
    int index;
    int employee_number;
    std::string name;
    std::string department;
    std::string band;
    std::string location;
};

std::string filename("empdata.txt");
// assuming the file is text
std::fstream fs;
fs.open(filename.c_str(), std::ios::in);
if(fs.fail())  return false;   
const char *whitespace    = " trnf";
const char *delimiter    = "|";
std::vector<Employee> employee_data;
// process each line in turn
while( std::getline(fs, line ) )
{
// removing leading and trailing whitespace
// can prevent parsing problemsfrom different line endings.
    strtk::remove_leading_trailing(whitespace, line);

    // strtk::parse combines multiple delimeters in these cases
    Employee e;
    if( strtk::parse(line, delimiter, e.index, e.employee_number, e.name, e.department, e.band, e.location) )
    {
         std::cout << "succeed" << std::endl;
     employee_data.push_back( e );
    }
}

我敢说,没有现成的东西可以做到这一点。但是你有自己构建它的所有工具

C方式

将行读入char *(使用cin.getline()),然后使用strtok和strcpy

getline方式

getline函数接受第三个参数来指定分隔符。你可以利用它来通过一个istringstream来分割线。例如:

int main() {
    std::string line, temp;
    std::ifstream myfile("file.txt");
    std::getline(myfile, line);
    while (myfile.good()) {
        empdata data;
        std::getline(myfile, line);
        if (myfile.eof()) {
            break;
        }
        std::istringstream istr(line);
        std::getline(istr, temp, '|');
        data.sl = ::strtol(temp.c_str(), NULL, 10);
        std::getline(istr, temp, '|');
        data.empNO = ::strtol(temp.c_str(), NULL, 10);
        istr.getline(data.name, sizeof(data.name), '|');
        istr.getline(data.department, sizeof(data.department), '|');
        istr.getline(data.band, sizeof(data.band), '|');
        istr.getline(data.location, sizeof(data.location), '|');
    }
    return 0;
}

这是前一个

的c++版本

找到方法

您将行读入字符串(如您目前所做的),并使用string::find(char sep, size_t pos)查找分隔符的下一个出现,并将子字符串开始和分隔符之间的数据(从string::c_str())复制到您的字段

手动方式

你只是迭代字符串。如果字符是分隔符,则在当前字段的末尾放置NULL并传递给下一个字段。

选择哪一个?

如果你更习惯其中一个,坚持使用它。

以下只是我的观点。

getline方式将是最简单的编码和维护。

发现方式是中等水平。它仍然处于相当高的水平,并且避免了istringstream的使用。

手动方式将是非常低级的,因此您应该构建它以使其可维护。例如,您可以将行明确描述为具有最大大小和当前位置的字段数组。由于您同时拥有int和char[]字段,这将会很棘手。但是您可以很容易地按照您想要的方式配置它。例如,您的代码只允许department字段有20个字符,而第2行的Research and Development字段更长。如果没有特殊处理,getline方法将使istringstream处于坏状态,并且不会再读取任何内容。即使你在州里过关了,你的处境也很不利。因此,您应该首先读入std::string,然后将开头复制到char *字段。

这是一个工作手册实现:

class Field {
public:
    virtual void reset() = 0;
    virtual void add(empdata& data, char c) = 0;
};
class IField: public Field {
private:
    int (empdata::*data_field);
    bool ok;
public:
    IField(int (empdata::*field)): data_field(field) {
        ok = true;
        reset();
    }
    void reset() { ok = true; }
    void add(empdata& data, char c);
};
void IField::add(empdata& data, char c) {
    if (ok) {
        if ((c >= '0') && (c <= '9')) {
            data.*data_field = data.*data_field * 10  + (c - '0');
        }
        else {
            ok = false;
        }
    }
}

class CField: public Field {
private:
    char (empdata::*data_field);
    size_t current_pos;
    size_t size;
public:
    CField(char (empdata::*field), size_t size): data_field(field), size(size) {
        reset();
    }
    void reset() { current_pos = 0; }
    void add(empdata& data, char c);
};
void CField::add(empdata& data, char c) {
    if (current_pos < size) {
        char *ix = &(data.*data_field);
        ix[current_pos ++] = c;
        if (current_pos == size) {
            ix[size -1] = '';
            current_pos +=1;
        }
    }
}
int main() {
    std::string line, temp;
    std::ifstream myfile("file.txt");
    Field* fields[] = {
        new IField(&empdata::sl),
        new IField(&empdata::empNO),
        new CField(reinterpret_cast<char empdata::*>(&empdata::name), 20),
        new CField(reinterpret_cast<char empdata::*>(&empdata::department), 20),
        new CField(reinterpret_cast<char empdata::*>(&empdata::band), 3),
        new CField(reinterpret_cast<char empdata::*>(&empdata::location), 20),
        NULL
    };
    std::getline(myfile, line);
    while (myfile.good()) {
        Field** f = fields;
        empdata data = {0};
        std::getline(myfile, line);
        if (myfile.eof()) {
            break;
        }
        for (std::string::const_iterator it = line.begin(); it != line.end(); it++) {
            char c;
            c = *it;
            if (c == '|') {
                f += 1;
                if (*f == NULL) {
                    continue;
                }
                (*f)->reset();
            }
            else {
                (*f)->add(data, c);
            }
        }
        // do something with data ...
    }
    for(Field** f = fields; *f != NULL; f++) {
        free(*f);
    }
    return 0;
}

它直接健壮,高效,可维护:添加字段很容易,并且可以容忍输入文件中的错误。但它比其他的要长得多,需要更多的测试。所以我不建议在没有特殊原因的情况下使用它(需要接受多个分隔符,可选字段和动态顺序,…)

尝试这个简单的代码段,它将读取文件,并给出打印,您可以逐行读取,然后您可以根据需要使用它来处理。

数据:由你提供:在名为Data .txt的文件中。

package com.demo;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
public class Demo {
    public static void main(String a[]) {
        try {
            File file = new File("data.txt");
            FileReader fileReader = new FileReader(file);
            BufferedReader bufferReader = new BufferedReader(fileReader);
            String data;
            while ((data = bufferReader.readLine()) != null) {
                // data = br.readLine( );
                System.out.println(data);
            }   
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在控制台中,您将得到如下输出:

Sl|EmployeeID|Name|Department|Band|Location
1|327427|Brock Mcneil|Research and Development|U2|Pune
2|310456|Acton Golden|Advertising|P3|Hyderabad
3|305540|Hollee Camacho|Payroll|U3|Bangalore
4|218801|Simone Myers|Public Relations|U3|Pune
5|144051|Eaton Benson|Advertising|P1|Chennai

这是一个简单的想法,你可以做你需要的

在c++中,您可以更改区域设置以在当前区域设置的分隔符列表中添加一个额外的字符:

#include <locale>
#include <iostream>
struct pipe_is_space : std::ctype<char> {
  pipe_is_space() : std::ctype<char>(get_table()) {}
  static mask const* get_table()
  {
    static mask rc[table_size];
    rc['|'] = std::ctype_base::space;
    rc['n'] = std::ctype_base::space;
    return &rc[0];
  }
};
int main() {
  using std::string;
  using std::cin;
  using std::locale;
  cin.imbue(locale(cin.getloc(), new pipe_is_space));
  string word;
  while(cin >> word) {
    std::cout << word << "n";
  }
}