避免重复代码

时间:2010-06-25 08:59:09

标签: c c99

让我说我有:

  switch( choice ) {
  case A:   
     stmt;
     do_stmt_related2A;
  break;

  case B:
     stmt;
     do_stmt_related2B;
  break;

  case C: something_different();
   ...
  }

我怎样才能避免重复stmt代码?

但是有没有解决方法? gcc扩展标签作为值看起来非常适合这种情况。

   switch( choice ) {
     do {
     case A:  ptr = &&A_label;
     break;
     case B:  ptr = &&B_label;
     } while(0);
              stmt;
              goto *ptr;
     case C: ...

有没有任何技巧可以在ANSI-C中做同样的事情? 编辑:我当然想到了函数/宏/内联。还有别的吗? 这也与表现无关。仅用于教育目的。 ;)

15 个答案:

答案 0 :(得分:29)

为什么不重构stmt(我假设这是一大块指令而不是单行)到它自己的函数do_stmt()并调用它?类似的东西:

switch( choice ) {
    case A:
        do_stmt();
        do_stmt_related2A;
        break;
    case B:
        do_stmt();
        do_stmt_related2B;
        break;
    case C: something_different();
        ...
}

那个gcc技巧真的很可怕。我希望任何一天都能在这些怪物身上找到可读的代码。

你应该总是假设继承你代码的程序员是一个知道你住在哪里的杀人狂: - )

答案 1 :(得分:17)

  

我怎样才能避免重复stmt代码?

将它放入一个函数并调用它。

而且,不,你 知道这是否会降低你的应用程序的速度,直到你对其进行分析并发现它是瓶颈。 (如果确实如此,请使用宏,或者如果是C99,则使用函数inline。)

答案 2 :(得分:9)

除了将公共代码重构为子函数的常见情绪之外,使用标准C功能重构代码的最简单方法可能是:

if (choice == A || choice == B) {
    stmt;
}

switch( choice ) {
  case A:   
    do_stmt_related2A;
    break;

  case B:
    do_stmt_related2B;
    break;

  case C:
    something_different();
    ...
}

它干净地表达了您想要做的事情,它避免了切换器内部的开关,阻止某些编译器有效地优化代码。

答案 3 :(得分:4)

任何方式都会有一些代码 - 您可以拥有重复的代码或代码以避免重复。所以我很好奇stmt;代码到底有多复杂。

简单,干净的解决方案是将共享部分(stmt)移动到单独的功能中。

void do_shared_stmt(void) {
 stmt;
}
/* .... */
swtich(choise) {
case A:
  do_shared_stmt();
  do_stmt_related2A();
  break;
case B:
  do_shared_stmt();
  do_stmt_related2B();
  break;
case C:
  something_different();
/* ... */
}

另一种解决方案(可能是可接受的,取决于您的情况)是嵌套分支语句:

swtich(choise) {
case A:
case B:
  stmt;
  if(choise == A) {
    do_stmt_related2A();
  } else {̈́
    do_stmt_related2B();
  }
  break;
case C:
  something_different();
/* ... */
}

答案 4 :(得分:1)

我可能会做那样的事情:

void do_stmt(int choice)
{
    stmt;
    switch(choice)
    {
         case A:
             do_stmt_related2A;
             break;
         case B:
             do_stmt_related2B;
             break;
    }  
}
/* ... */
switch(choice)
{
    case A:
    case B:
        do_stmt(choice);
        break;
    case C:
         something_different();
...

答案 5 :(得分:0)

我会选择像我在这里添加的东西。当然,你可能有自己的想法有一个嵌套的switch语句,没什么新东西。此外,它还只评估choice一次。

它还避免了标签地址的gcc结构,所以这里没有间接。一个不错的编译器应该能够很好地优化这样的事情。

另请注意,my_choiceintmax_t,因此这应该与choice可能具有的任何类型兼容。

(放入for只是为了好玩,显然只适用于C99。你可以用额外的{}替换它,只需要my_choice的声明C89。)

typedef enum { one = 1, two, three } number;

int main(int argc, char** argv) {

  number choice = (number)argc;

  for (intmax_t _0 = 0, my_choice = choice; !_0; ++_0)
    switch(my_choice) {
    case one:;
    case two:;
      {
        printf("Do whatever you want here\n");
        switch (my_choice) {
        case one:
          printf("test 1\n");
          break;
        case two:
          printf("test 2\n");
          break;
        }
        printf("end special\n");
      }
      break;
    default:;
      printf("test %d, default\n", choice);
    }
}

答案 6 :(得分:0)

使用goto指针可能会导致代码变慢,因为它会关闭某些gcc的其他优化(或者我最后一次读取它)。 Gcc基本上认为它可能太复杂而无法跟上可能发生的事情,并假设更多的分支指令可以针对每个goto标签已经&&比实际情况。如果您坚持尝试使用此方法,我建议您尝试使用整数和另一个开关/案例而不是goto。 Gcc希望能够理解这一点。

除此之外,对于许多陈述而言,这可能不值得工作,或者它实际上可能更好地工作。它确实很大程度上取决于stmt实际上是什么。

如果stmt确实很昂贵或代码很大,则将static重构为stmt函数可能会产生良好的效果。

如果可以,可以尝试将stmt提升出开关/案例,并执行始终执行它。有时这是最便宜的方式,但这确实取决于stmt实际做了什么。

您可能要做的另一件事是重构所有stmtdo_stmt_related2Ado_stmt_related2A所有文件static函数,如下所示:

// args in this is just a psudocode place holder for whatever arguments are needed, and 
// not valide C code.
static void stmt_f(void (*continuation)(arg_list), args) {
   stmt; // This corresponds almost exactly to stmt in your code
   continuation(args);
}
static void do_stmt_related2A_f(args) {
    do_stmt_related2A;
}
static void do_stmp_related2B_f(args) {
    do_stmt_related2B;
}

...
    switch (condition) {
       case A:
          stmt_f(do_stmt_related2A_f, args);
          break;
       case B:
    ...

stmt_f结束时对continuation函数的调用是尾调用,很可能成为跳转而不是真正的调用。因为所有这些都是静态的,所以编译器可能会看到整个值集,这些值可以是延续函数并且可以进一步优化,但我不知道。

除非stmt非常大,否则很可能这是一个微观优化,这是不值得的。如果你真的想知道那么你应该编译成汇编并尝试查看编译器对原始代码的真正作用。它可能比你想象的更好。

哦,您可能尝试的最后一件事是,如果您控制实际值A,B,C ..可以采取什么,那么您可以确保具有相似前缀的那些具有相邻值。如果A和B确实彼此相邻,并且如果编译器决定它需要将switch / case分解为几个不同的跳转表,那么它可能会将A和B放在同一个跳转表中,并且看到它们有为您编码的相同前缀和升降机。如果不共享该前缀的C与A或B不相邻,则更有可能,但随后您的整体代码可能会更糟。

答案 7 :(得分:0)

您只需要两个控制结构。一个用于指示执行一阶指令,另一个用于指令用于二阶指令。

switch (state) {
    case A:
    case B:
        stmt;

        switch (state) {
            case A:
                do_stmt_related2A;
                break;
            case B:
                do_stmt_related2B;
                break;
        }

        break;

    case C:
        something_different();
        ...
}

值得注意的是switch不太适合二阶控制结构,您可能希望使用更传统的条件分支。最后,关于goto-label-pointer的问题的真正答案是这是子程序链接的用途。如果您的说明比单个表达式更复杂,那么您可以而且应该使用函数。

答案 8 :(得分:0)

以下是函数调用或辅助开关语句的替代方法:

isA=false;
switch( choice ) { 
  case A:    
    isA=true;
  //nobreak
  case B: 
    stmt; 
    isA ? do_stmt_related2A : do_stmt_related2B;
  break; 
  case C: 
    something_different(); 
  break;
} 

但是,我不能说我真的推荐它作为编码风格。

答案 9 :(得分:0)

如果您使用的是gcc,则一个选项是 nested functions 。这些函数可以访问父函数的所有变量。

例如:

void foo(int bar) {

    int x = 0;
    void stmt(void) { //nested function!!
        x++;
        if (x == 8) {
            x = 0;
        }
    }

    switch( bar ) {
    case A:   
        stmt();
        do_stmt_related2A;
    break;

    case B:
        stmt();
        do_stmt_related2B;
    break;

    case C:
        something_different();
        ...
    break;
    }
}

由于这是gcc扩展,因此显然不应该用于可移植的代码。但玩它可能很有趣:)在某些情况下使代码更具可读性。

如果你有一个可执行堆栈,你甚至可以创建一个指向嵌套函数的指针。这很像一个lambda函数,除了可怕 - 或非常有趣,取决于你的观点。 (就像所有指向“本地”对象的指针一样,当父函数退出时指针变为无效!小心!)

答案 10 :(得分:0)

回到不同案例值的原始含义。案例A,B和C代表什么?如果A和B应该部分执行相同的代码,它们应该有一些相似之处。找到它们并以另一种方式表达它们。

以下示例使此更清晰。假设A,B和C是3种不同的动物,A和B的重复代码实际上是可以飞行的动物的典型代码。然后你可以像这样编写代码:

if (can_fly(choice))
   {
   stmt;
   }

switch( choice )
  {
  case A:   
     do_stmt_related2A;
     break;
  case B:
     do_stmt_related2B;
     break;
  case C:
     something_different();
     break;
  }

这样,如果稍后将第三个选项D添加到您的应用程序中,则忘记“stmt”重复代码的可能性会略小。

如果你真的想要阻止函数调用(对于can_fly),并且A,B和C是数字常量,你可以使用位掩码。在这个例子中,我们使用一位来表示动物可以飞行:

if (choice & BIT_CAN_FLY)
   {
   stmt;
   }

switch( choice )
  {
  case A:   
     do_stmt_related2A;
     break;
  case B:
     do_stmt_related2B;
     break;
  case C:
     something_different();
     break;
  }

答案 11 :(得分:0)

您可以轻松地将stmt代码放入一个函数中并从您的案例或任何地方调用该函数,您可以多次调用函数。

答案 12 :(得分:0)

我只看到两种可能的逻辑安排。

L1:
 val
 |
 |-------------------
 |        |         |
 A        B         C
 |        |         |
 stmt     stmt      crap
 |        |
 Afunk    Bfunk

L2:
 val
 |
 |-------------------
 stmt               |
 |---------         |
 |        |         |
 A        B         C
 |        |         |
 Afunk    Bfunk     crap

在L1中,对stmt使用内联函数,或者如果分支两次就可以使用L2。如果你正在谈论src代码重复(而不是二进制文件中的重复),只需内联函数即可解决你的问题。

答案 13 :(得分:0)

其他人已经给出了这个答案,但我想以正式的方式陈述。

如果您不关心性能,那么您已经正确识别出duplicate code典型的“代码味道”之一。

最基本的重构之一称为Extract Method。学习它,生活,爱它。识别重复代码并将其删除的过程应该是您每分钟工作流程的一部分。

答案 14 :(得分:0)

这可能是你应该忽略的(但这很有趣):

// function pointer to B function
void (*func)(void) = do_stmt_related2B;
switch(choice) {
case A:
    func = do_stmt_related2A; // change function pointer to A function
case B:
    stmt;
    func(); // call function pointer
    break;

case C:
    something_different();
    ...
}

可能比其他解决方案更高效/更小/可读/可取/有趣。只是解决这个问题的有趣方法,没有人提到过。