1

我正处于 Python 的幼虫阶段和 C++ 的 pre-egg 阶段,但我正在努力做到最好,特别是“不要重复自己”的原则。

我有一个要打开的多通道原始文件格式,带有一个主要的 ascii 标头,其中的字段可表示为字符串和整数(始终编码为用空格填充的字符)。第二部分是 N 个标头,其中 N 是主标头的一个字段,每个标头本身都有更多的文本和数字字段(编码为 ascii),指的是实际 16 位多通道流的长度和大小构成文件的其余部分。

到目前为止,我在 C++ 中有这个工作代码:

#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
#include <map>

using namespace std;

struct Header {
    string version;
    string patinfo;
    string recinfo;
    string start_date;
    string start_time;
    int header_bytes;
    string reserved;
    int nrecs;
    double rec_duration;
    int nchannels;
};

struct Channel {
    string label;
    string transducertype;
    string phys_dim;
    int pmin;
    int pmax;
    int dmin;
    int dmax;
    string prefiltering;
    int n_samples;
    string reserved;
};


int main()
{
    ifstream edf("/home/helton/Dropbox/01MIOTEC/06APNÉIA/Samples/Osas2002plusQRS.rec", ios::binary);

    // prepare to read file header
    Header header;
    char buffer[80];

    // reads header fields into the struct 'header'
    edf.read(buffer, 8);
    header.version = string(buffer, 8);

    edf.read(buffer, 80);
    header.patinfo = string(buffer, 80);

    edf.read(buffer, 80);
    header.recinfo = string(buffer, 80);

    edf.read(buffer, 8);
    header.start_date = string(buffer, 8);

    edf.read(buffer, 8);
    header.start_time = string(buffer, 8);

    edf.read(buffer, 8);
    stringstream(buffer) >> header.header_bytes;

    edf.read(buffer, 44);
    header.reserved = string(buffer, 44);

    edf.read(buffer, 8);
    stringstream(buffer) >> header.nrecs;

    edf.read(buffer,8);
    stringstream(buffer) >> header.rec_duration;

    edf.read(buffer,4);
    stringstream(buffer) >> header.nchannels;

    /*
    cout << "'" << header.version << "'" << endl;
    cout << "'" << header.patinfo << "'" << endl;
    cout << "'" << header.recinfo << "'" << endl;
    cout << "'" << header.start_date << "'" << endl;
    cout << "'" << header.start_time << "'" << endl;
    cout << "'" << header.header_bytes << "'" << endl;
    cout << "'" << header.reserved << "'" << endl;
    cout << "'" << header.nrecs << "'" << endl;
    cout << "'" << header.rec_duration << "'" << endl;
    cout << "'" << header.nchannels << "'" << endl;
    */

    // prepare to read channel headers
    int ns = header.nchannels; // ns tells how much channels I have
    char title[16]; // 16 is the specified length of the "label" field of each channel

    for (int n = 0; n < ns; n++)
    {
        edf >> title;
        cout << title << endl; // and this successfully echoes the label of each channel
    }


    return 0;
};

我已经不得不发表一些评论:

  • 我选择使用 struct 是因为格式规范是硬编码的;
  • 我没有遍历主要的头字段,因为在我看来要读取的字节数和类型相当随意;
  • 既然我成功地获得了每个通道的标签,我实际上将为每个通道的字段创建结构,这些字段本身可能必须存储在地图中。

我的(希望是直截了当的)问题是:

“我应该担心偷工减料以使这种代码更‘Pythonic’(更抽象,更少重复),还是这不是 C++ 中的工作方式?”

许多 Python 布道者(就像我自己一样,因为我喜欢它)强调了它的易用性和所有这些。所以,我会想知道我是在做愚蠢的事情还是只做正确的事情,但由于 C++ 的本质,我不会那么“自动”。

谢谢阅读

赫尔顿

4

5 回答 5

5

我会说没有 Pythonic C++ 代码这样的东西。DRY 原则适用于两种语言,但大部分被认为是“Pythonic”的东西只是使用 Python 特定的结构在 Python 中表达逻辑的最短、最甜美的方式。惯用的 C++ 完全不同。

lambda例如,有时不被认为是非常 Pythonic 并且保留用于不存在其他解决方案的情况,而只是被添加到 C++ 标准中。C++ 没有关键字参数,这是非常 Pythonic 的。C++ 程序员不喜欢map在不必要的时候构造 a,而 Python 程序员可能会抛出dict很多问题,他们只是碰巧使意图比有效的替代方案更清晰。

如果您想节省打字,请使用我之前发布的功能,然后:

header.version = read_field(edf, 8);
header.patinfo = read_field(edf, 80);

这应该可以为您节省不少行数。但比这几行更重要的是您已经实现了少量模块化:如何读取字段以及读取哪些字段现在是程序的独立部分。

于 2011-04-14T20:26:41.820 回答
2

你是对的:正如所写,代码是重复的(并且没有错误检查)。您读取的每个字段实际上都需要您采取三到五个步骤,具体取决于正在读取的数据类型:

  1. 从流中读取字段
  2. 确保读取成功
  3. 解析数据(如有必要)
  4. 确保解析成功(如有必要)
  5. 将数据复制到目标位置

您可以将所有这三个包装到一个函数中,以减少代码的重复性。例如,考虑以下函数模板:

template <typename TStream, typename TResult>
void ReadFixedWidthFieldFromStream(TStream& str, TResult& result, unsigned sz) 
{
    std::vector<char> data(sz);

    if (!str.read(&data[0], sz))
        throw std::runtime_error("Failed to read from stream");

    std::stringstream ss(&data[0]);
    if (!(ss >> result))
        throw std::runtime_error("Failed to parse data from stream");
}

// Overload for std::string:
template <typename TStream>
void ReadFixedWidthFieldFromStream(TStream& str, std::string& result, unsigned sz) 
{
    std::vector<char> data(sz);

    if (!str.read(&data[0], sz))
        throw std::runtime_error("Failed to read from stream");

    result = std::string(&data[0], sz);
}

现在您的代码可以更加简洁:

ReadFixedWidthFieldFromStream(edf, header.version, 8);
ReadFixedWidthFieldFromStream(edf, header.patinfo, 80);
ReadFixedWidthFieldFromStream(edf, header.recinfo, 80);
// etc.
于 2011-04-14T20:22:38.777 回答
1

此代码简单明了,易于理解。如果它有效,请不要浪费时间更改它。我确信有很多写得不好、复杂且难以理解(而且可能不正确)的代码应该首先修复:)

于 2011-04-14T20:21:26.073 回答
0

要直接从字符串中读取文件,请参阅这个问题The rest is wrong但我个人认为有更好/更清洁的方法来做到这一点。

如果您知道结构的大小不使用字符串,请使用原始 C 类型(并确保结构已打包)。请参阅以下链接: http: //msdn.microsoft.com/en-us/library/2e70t5y1 (v=vs.80).aspx & http://gcc.gnu.org/onlinedocs/gcc-3.2.3/gcc /Type-Attributes.html

例如,我会这样做(不确定每个字符串的大小,但你明白了):

struct Header {
    char version[8];
    char patinfo[80];
    char recinfo[80];
    char start_date[8];
    char start_time[8];
    int header_bytes;
    char reserved[44];
    int nrecs;
    double rec_duration;
    int nchannels;
};

一旦你有一个打包的结构,你可以直接从文件中读取它:

struct Header h;
edf.read(&h,sizeof(struct Header));

对我来说,这是最干净的方法,但请记住,您必须打包结构,以保证内存中的结构与保存在文件中的结构具有相同的大小 - 这并不难看,而测试。

于 2011-04-14T20:32:33.303 回答
0

Python 之禅并没有明确提到 DRY。

>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
于 2011-04-14T20:36:36.437 回答