在C++中为了“Pythonic”应该避免重复代码吗?怎么做?
我现在在学习Python,水平还很初级,而C++的学习也刚开始,但我正在尽力而为,特别是遵循“不要重复自己”的原则。
我需要打开一个多通道的原始文件格式,这个文件有一个主要的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;
};
我有几点想法:
- 我选择使用结构体,因为格式规范非常固定;
- 我没有遍历主头部的字段,因为读取的字节数和类型对我来说似乎有点随意;
- 现在我成功获取了每个通道的标签,我实际上会为每个通道的字段创建结构体,这些字段可能需要存储在一个映射中。
我想问的(希望简单明了)问题是:
“我应该担心为了让代码更‘Pythonic’(更抽象,少重复)而走捷径吗?还是说在C++中并不是这样工作的?”
很多Python的支持者(我自己也是,因为我很喜欢它)都强调它的易用性等等。所以,我会想一段时间,我是不是在做傻事,还是在做对的事情,只是因为C++的特性没有那么“自动化”。
谢谢你的阅读
Helton
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.
我觉得没有所谓的“Pythonic C++代码”。虽然这两种语言都遵循DRY原则(不要重复自己),但很多被认为是“Pythonic”的写法,其实只是用Python特有的方式来简洁明了地表达逻辑。而“地道的C++”则完全不同。
比如,lambda
在Python中有时候不被认为是很“Pythonic”,通常只在没有其他解决方案时才用,但它现在正被加入到C++标准中。C++没有像Python那样的关键字参数,这在Python中是很常见的。C++程序员不喜欢在不必要的时候构建一个map
,而Python程序员可能会在很多情况下使用dict
,因为这样能让意图更清晰,即使效率上可能不是最优的选择。
如果你想省点打字,可以使用我之前提到的那个函数,然后:
header.version = read_field(edf, 8);
header.patinfo = read_field(edf, 80);
这样可以省去不少代码行。但比起省代码行,更重要的是你实现了一定的模块化:如何读取一个字段和读取哪些字段现在是你程序的两个独立部分。