#define的适当使用是否可以简化重复代码的键入?


17

对于使用#define定义完整的代码行来简化编码是否有好的看法还是有什么看法?例如,如果我需要一起打印一堆单词,我会很讨厌打字

<< " " <<

在cout语句中的单词之间插入空格。我可以做

#define pSpace << " " <<

和类型

cout << word1 pSpace word2 << endl;

对我来说,这既不会增加代码的清晰度,也不会减少代码的清晰度,并且使键入变得更加容易。在其他情况下,我可以想到在哪里键入通常会更容易进行调试。

有什么想法吗?

编辑:感谢所有的伟大的答案!在进行了很多重复的键入后,这个问题才出现在我的头上,但是我从没想过会使用其他的,不那么混乱的宏。对于不想阅读所有答案的人,最好的选择是使用IDE的宏来减少重复的键入。


74
这对您很清楚,因为您是发明它的。对其他人来说,它只是令人困惑。首先,它看起来像语法错误。当它编译时,我会想一想,然后发现您有一个并非全部大写的宏。在我看来,这只会使代码难以维护,如果将其用于代码审查,我肯定会拒绝这样做,而且我不希望您会发现很多会接受的代码。您将节省3个字符!!!!!
马丁·约克

11
在无法使用函数或其他方法合理地排除重复性的地方,更好的方法是学习编辑器或IDE可以做什么来帮助您。文本编辑器宏或“代码段”热键可以使您不必键入太多内容,而不会损害可读性。
Steve314,2011年

2
我之前已经做过(使用较大的样板块),但是我的实践是编写代码,然后运行预处理器,并将原始文件替换为预处理器输出。节省了我的打字时间,免除了我(和其他人)的维护麻烦。
TMN

9
您保存了3个字符,并将它们换成了令人困惑的陈述。坏宏的很好例子,恕我直言:o)
MaR 2011年

7
许多编辑器都具有针对此场景的高级功能,称为“复制和粘贴”
克里斯·伯特·布朗

Answers:


111

编写代码很容易。读取代码很难。

您只编写一次代码。它生活了很多年,人们读了一百遍。

优化代码以供阅读,而不是用于编写。


11
我同意100%。(实际上,我本人将自己写这个答案。)代码只编写了一次,但可能被读取数十次,数百次甚至数千次,也许被数十次,数百次甚至数千次开发。编写代码所花的时间完全无关紧要,唯一重要的是阅读和理解它的时间。
2011年

2
预处理程序可以并且应该用于优化代码的读取和维护。
SK-logic

2
即使只是您在一两年内阅读代码:在做其他事情的同时,您自己也会忘记这些事情。
johannes

2
“总是编码,好像最终维护您的代码的那个人将是一个暴力的精神病患者,知道您的住所。” -(Martin Golding)
迪伦·雅加

@Dylan-否则,在维持该代码几个月后,他找到您-(Me)
Steve314,2011年

28

就个人而言,我讨厌它。我劝阻人们使用此技术的原因有很多:

  1. 在编译时,您实际的代码更改可能很重要。下一个家伙出现了,甚至在他的#define或函数调用中加入了右括号。在代码的特定点编写的内容与经过预处理后的内容相去甚远。

  2. 这是不可读的。对您来说可能现在很清楚..如果仅仅是这个定义。如果它成为一种习惯,您很快就会以数十种#defines告终,并开始迷失自己。但最糟糕的是,没有其他人将能够理解word1 pSpace word2确切的含义(无需查找#define)。

  3. 外部工具可能会成为问题。假设您以#define结尾,但其中包含一个右括号,但没有一个右括号。一切都可能运行良好,但是编辑器和其他工具可能看起来function(withSomeCoolDefine;很奇怪(即,他们将报告错误,但不会)。(类似的示例:定义内的函数调用-您的分析工具将能够找到此调用吗?)

  4. 维护变得更加困难。除了维护带来的常见问题之外,您还具有所有这些定义。除了上述几点,重构的工具支持也可能受到负面影响。


4
由于试图在Doxygen中正确处理它们的麻烦,我最终禁止了我使用几乎所有的宏。到现在已经好几年了,总的来说,我认为可读性有了很大提高-无论我是否使用Doxygen。
2011年

16

我对此的主要想法是,在编写代码时,我永远不会使用“使键入变得更容易”的规则。

编写代码时,我的主要原则是使其易于阅读。这样做的基本原理是,读取的代码要比编写的代码多一个数量级。这样一来,您失去认真,有序,正确布局的时间实际上是在花更多时间阅读和理解上的时间。

因此,您使用的#define只是打破了altered <<other-stuff的常规方式。它打破了最不令人惊讶的规则,而且恕我直言,这不是一件好事。


1
+1:“读取的代码比编写的代码多一个数量级”!
乔治

14

这个问题给出了一个清晰的示例,说明了如何严重使用宏。要查看其他示例(并得到娱乐),请参阅此问题

话虽如此,我将在现实世界中举例说明我认为很好地合并了宏。

第一个示例出现在CppUnit中,这是一个单元测试框架。像其他任何标准测试框架一样,您创建一个测试类,然后必须以某种方式指定哪些方法应作为测试的一部分运行。

#include <cppunit/extensions/HelperMacros.h>

class ComplexNumberTest : public CppUnit::TestFixture  
{
    CPPUNIT_TEST_SUITE( ComplexNumberTest );
    CPPUNIT_TEST( testEquality );
    CPPUNIT_TEST( testAddition );
    CPPUNIT_TEST_SUITE_END();

 private:
     Complex *m_10_1, *m_1_1, *m_11_2;
 public:
     void setUp();
     void tearDown();
     void testEquality();
     void testAddition();
}

如您所见,该类的第一个元素是一个宏块。如果我添加了一个新方法testSubtraction,那么很明显您需要做些什么才能使其包含在测试运行中。

这些宏块扩展为如下所示:

public: 
  static CppUnit::Test *suite()
  {
    CppUnit::TestSuite *suiteOfTests = new CppUnit::TestSuite( "ComplexNumberTest" );
    suiteOfTests->addTest( new CppUnit::TestCaller<ComplexNumberTest>( 
                                   "testEquality", 
                                   &ComplexNumberTest::testEquality ) );
    suiteOfTests->addTest( new CppUnit::TestCaller<ComplexNumberTest>(
                                   "testAddition",
                                   &ComplexNumberTest::testAddition ) );
    return suiteOfTests;
  }

您想阅读和维护哪一项?

另一个示例在Microsoft MFC框架中,您可以在其中将函数映射到消息:

BEGIN_MESSAGE_MAP( CMyWnd, CMyParentWndClass )
    ON_MESSAGE( WM_MYMESSAGE, OnMyMessage )
    ON_COMMAND_RANGE(ID_FILE_MENUITEM1, ID_FILE_MENUITEM3, OnFileMenuItems)
    // ... Possibly more entries to handle additional messages
END_MESSAGE_MAP( )

那么,什么是将“好宏”与可怕的邪恶区别开的呢?

  • 他们执行一项无法通过其他任何方式简化的任务。编写宏来确定两个元素之间的最大值是错误的,因为可以使用模板方法来实现相同的目的。但是,有些复杂的任务(例如,将消息代码映射到成员函数)是C ++语言无法很好地处理的。

  • 它们具有极其严格的正式用法。在这两个示例中,宏块都是通过开始和结束宏来声明的,而中间的宏将永远只出现在这些块内部。您拥有普通的C ++,您短暂地使用了一个宏块,然后再次回到普通状态。在“邪恶的宏”示例中,宏散布在整个代码中,不幸的读者无法知道C ++规则何时适用以及何时不适用。


5

如果您调整自己喜欢的IDE /文本编辑器以插入代码片段,发现您感到厌烦地反复键入,这将是更好的选择。最好将“礼貌”一词进行比较。实际上,当预处理优于编辑器的宏时,我无法考虑任何类似的情况。好吧,也许是一个-当出于某些神秘和不愉快的原因,您经常使用不同的工具集进行编码时。但这不是理由:)

当文本预处理的工作变得更加难以理解和复杂时(考虑参数化的输入),它对于更复杂的场景也可能是更好的解决方案。


2
+1。确实:让编辑为您完成工作。例如,如果您将缩写为,则将两全其美<< " " <<
unperson325680 2011年

-1表示“对于更复杂的场景,当文本预处理可以使事情变得更加难以理解和复杂(考虑参数化的输入)时,它也可能是一个更好的解决方案”-如果那么复杂,请为其建立一种方法,即使如此,一种方法。例如,我最近在代码中发现了这种罪恶..... #define printError(x){puts(x); return x}
mattnz 2011年

@mattnz,我的意思是循环构造,if / else构造,用于创建比较器的模板等等,这些都是好东西。在IDE中,这种参数化的输入不仅可以帮助您快速键入几行代码,还可以快速遍历参数。没有人试图与方法竞争。方法就是方法)))
shabunc 2011年

4

其他人已经解释了为什么您不应该这样做。您的示例显然不值得使用宏来实现。但是,在很多情况下, 为了易于阅读,必须使用宏。

明智地应用这种技术的一个臭名昭著的例子是Clang项目:了解.def在那里如何使用文件。使用宏,#include您可以为一组类似的事物提供一个有时是完全声明性的定义,这些定义将被展开为类型声明,case适当的语句,默认的初始化程序等。它大大提高了可维护性:您永远不会忘记添加新的例如,case当您添加了new时enum,语句随处可见。

因此,与任何其他强大的工具一样,您必须谨慎使用C预处理程序。编程领域没有通用的规则,例如“您永远不要使用它”或“您总是必须使用它”。所有规则不过是准则。


3

像这样使用#defines永远是不合适的。就您而言,您可以执行以下操作:

class MyCout 
{
public:
  MyCout (ostream &out) : m_out (out), m_space_pending (false)
  {
  }

  template <class T>
  MyCout &operator << (T &value)
  { 
    if (m_space_pending)
      m_out << " ";

    m_out << value;
    m_space_pending = false;
    return *this;
  }

  MyCout &operator << (const char *value)
  {
    if (m_space_pending)
      m_out << " ";

    m_out << value;
    m_space_pending = true;
    return *this;
  }

  MyCout &operator << (char *value) { return operator << (static_cast <const char *> (value)); }
  MyCout &operator << (ostream& (*fn)(ostream&)) { m_out << fn; return *this; }

private:
  ostream
    &m_out;

  bool
    m_space_pending;
};

int main (int argc, char *argv [])
{
  MyCout
    space_separated (cout);

  space_separated << "Hello" << "World" << endl;
}

2

没有。

对于打算在代码中使用的宏,测试适当性的一个很好的指导原则是用括号(对于表达式)或花括号(对于代码)包围它的扩展,并查看它是否仍可以编译:

// These don't compile:

#define pSpace (<< " " <<)
cout << word1 pSpace word2 << endl;

#define space(x) (" " << (x))
cout << word1 << space(word2) << endl;

// These do:

#define FOO_FACTOR (38)
x = y * FOO_FACTOR;

#define foo() (cout << "Foo" << endl)
foo();

#define die(c) { if ((c)) { exit(1); } }
die(foo > 8);

#define space(x) (" " + string((x)))
cout << "foo" << space("bar") << endl;

声明中使用的宏(例如,Andrew Shepherd的答案中的示例)可以使用较宽松的规则集,只要它们不会破坏周围的上下文(例如,在public和之间切换private)即可。


1

在纯“ C”程序中这样做是相当合理的事情。

在C ++程序中,这是不必要且令人困惑的。

有很多方法可以避免在C ++中重复输入代码。通过使用您的IDE提供的功能(即使使用vi,简单的“ %s/ pspace /<< " " <</g”也可以节省尽可能多的键入并仍然生成标准的可读代码)。您可以定义一个私有方法来实现此目的,或者对于更复杂的情况,C ++模板将更加简洁。


2
不,在纯C语言中这样做不是合理有效的事情。单值或完全独立的表达式仅依赖于宏参数,是的,对于后者,函数甚至可能是更好的选择。像示例中一样,这种半生不熟的构造绝对不可能。
确保

@secure-我同意在给出的示例中这不是一个好主意。但由于缺乏模板等,还有在C.“#DEFINE”宏有效的用途
詹姆斯-安德森

1

在C ++中,可以通过运算符重载来解决。甚至是像可变参数函数一样简单的东西:

lineWithSpaces(word1, word2, word3, ..., wordn)既简单又节省您pSpaces一次又一次的键入。

因此,在您看来,这似乎没什么大不了的,但是有一个更简单,更强大的解决方案。

通常,很少有使用宏的时间大大缩短而不会引起混淆的情况,并且大多数情况下,使用实际语言功能的解决方案就足够短了(宏只是单纯的字符串替换)。


0

对于使用#define定义完整的代码行来简化编码是否有好的看法还是有什么看法?

是的,这很糟糕。我什至看到有人这样做:

#define R return

保存键入(您要实现的目标)。

这样的代码只属于像的地方这样


-1

宏是邪恶的,只有在确实需要时才应使用宏。在某些情况下,适用宏(主要是调试)。但是在大多数情况下,在C ++中,您可以使用内联函数。


2
任何编程技术都没有内在的邪恶。只要您知道自己在做什么,就可以使用所有可能的工具和方法。它适用于臭名昭著的goto,所有可能的宏观系统等
SK-逻辑

1
在这种情况下,这恰恰是邪恶的定义:“某些情况下您应该避免大部分时间,而某些情况下则应始终避免”。在链接上解释了邪恶所指向的地方。
sakisk 2011年

2
我相信将任何东西冠以“邪恶”,“潜在有害”甚至“可疑”的标签是适得其反的。我不喜欢“不良实践”和“代码异味”的概念。每个开发人员都必须在每种特定情况下决定哪种做法有害。标记是有害的做法 -如果某些东西已经被其他人标记了,人们往往不会再做任何思考。
SK-logic

-2

不,您不允许使用宏保存输入内容

但是,您甚至可以使用它们将未更改的代码部分与更改的部分分开,并减少冗余。对于后者,您必须考虑替代方法,只有在更好的工具不起作用时才选择宏。(因为练习宏恰好位于行尾,所以拥有它意味着不得已...)

为了减少打字,大多数编辑器都具有宏,甚至是智能代码段。


“不,您不能使用宏来保存输入内容。” -好的,我不会在这里听您的订单!如果我有一堆需要声明/定义/映射/ switch/ etc的对象,那么您可以打赌我将使用宏来避免疯狂,并提高可读性。使用宏来保存关键字,控制流之类的输入是愚蠢的-但是,没有人可以使用它们在有效的上下文中保存击键同样愚蠢。
underscore_d
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.