【问题标题】:Is there a built-in way to swap two variables in C?有没有内置的方法来交换 C 中的两个变量?
【发布时间】:2010-04-14 13:46:04
【问题描述】:

我知道如何在 C++ 中交换两个变量,即你使用std::swap(a,b)

C标准库是否有和C++的std::swap()类似的功能,还是我必须自己定义?

【问题讨论】:

标签: c


【解决方案1】:

你需要自己定义。

  1. C 没有模板。

  2. 如果确实存在这样的函数,它看起来像void swap(void* a, void* b, size_t length),但与std::swap 不同的是,它不是类型安全的。

  3. 并且没有提示可以内联此类函数,如果交换频繁,这一点很重要(在 C99 中有 inline 关键字)。

  4. 我们也可以定义一个宏

     #define SWAP(a,b,type) {type ttttttttt=a;a=b;b=ttttttttt;}
    

    但它会影响ttttttttt 变量,您需要重复a 的类型。 (在 gcc 中有 typeof(a) 来解决这个问题,但你仍然不能 SWAP(ttttttttt,anything_else);。)

  5. 并且就地编写一个交换也不是那么难——它只是 3 行简单的代码!

【讨论】:

  • 为了避免遮蔽ttttttttt,(或者如果将该变量作为参数传入,则会导致错误)。你也可以调用变量SWAP
【解决方案2】:

C 中没有等价物——事实上不可能,因为 C 没有模板函数。您必须为要交换的所有类型编写单独的函数。

【讨论】:

  • 所以c没有标准库算法?交换是一个常用的功能
  • @Dr: C 没有默认的数据结构库。
  • @Dr Deo C 没有很多 C++ 做的事情——这就是重点。
  • @Ken 实际上,在 C++ 中,swap() 以各种“创造性”的方式使用!不可否认,其中大部分不适用于 C 代码。
  • 由于这通常在宏中完成,因此说 “您必须为要交换的所有类型编写单独的函数”并不完全正确
【解决方案3】:

如果你不介意使用 C 语言的 gcc 扩展,你可以用宏做类似的事情,typeof:

#include <stdio.h>

#define SWAP(a, b) do { typeof(a) temp = a; a = b; b = temp; } while (0)

int main(void)
{
    int a = 4, b = 5;
    float x = 4.0f, y = 5.0f;
    char *p1 = "Hello";
    char *p2 = "World";

    SWAP(a, b); // swap two ints, a and b
    SWAP(x, y); // swap two floats, x and y
    SWAP(p1, p2); // swap two char * pointers, p1 and p2

    printf("a = %d, b = %d\n", a, b);
    printf("x = %g, y = %g\n", x, y);
    printf("p1 = %s, p2 = %s\n", p1, p2);

    return 0;
}

【讨论】:

  • @Neil:是的,但 OP 专门要求 C 实现。这远不是一个完美的解决方案,但对于 C 应用程序 (IMNVHO) 的重要子集来说,它是一个合理的解决方案。
  • 你可以使用 C11 的 _generic 表达式来实现它。
  • @CasperBeyer 我看不出这对struct 类型有什么作用
  • 如果一个要使用 GCC 扩展,为什么不两个呢? :-) 使用 statement expression 而不是 do while
  • @GregA.Woods:语句表达式仅在您想要返回值时才有用。宏的 do{}while() 习语很容易理解,而且每个人都知道它是安全的,即使替换到 if (foo) SWAP() else ... 上下文中也是如此。如果是({...; b = temp; }),我可能不得不停下来想一想。不需要太多额外的心理时间就能看到这也没关系,但它不是零,我看不出让它评估为b(或a)的最终值的好处。跨度>
【解决方案4】:

这在 Clang 和 gcc 中运行很快(但不是 icc,它不识别此交换函数 - 但是,它可以在任何标准 C99 编译器中编译),前提是优化实际上识别交换(它们在足够高的情况下优化级别)。

#include <string.h>

#define SWAP(a, b) swap_internal(&(a), &(b), sizeof *(1 ? &(a) : &(b)))
static inline void swap_internal(void *a, void *b, size_t size) {
    char tmp[size];
    memcpy(tmp, a, size);
    memmove(a, b, size);
    memcpy(b, tmp, size);
}

现在解释它是如何工作的。首先,SWAP() 这一行比较奇怪,但其实比较简单。 &amp;(a) 是参数 a 作为指针传递。同样,&amp;(b) 是作为指针传递的参数b

最有趣的一段代码是sizeof *(1 ? &amp;(a) : &amp;(b))。这实际上是一个比较聪明的错误报告。如果不需要错误报告,它可能只是sizeof(a)。三元运算符要求其运算具有兼容的类型。在这种情况下,我通过将两个不同的参数转换为指针来检查它们的类型兼容性(否则,intdouble 将是兼容的)。由于int *double * 不兼容,因此编译会失败……前提是它是标准C 编译器。可悲的是,许多编译器在这种情况下假定 void * 类型,所以它失败了,但至少有一个警告(默认情况下启用)。为确保结果大小正确,该值被取消引用,并应用于sizeof,因此没有副作用。

~/c/swap $ gcc swap.c
swap.c: In function ‘main’:
swap.c:5:64: warning: pointer type mismatch in conditional expression [enabled by default]
 #define SWAP(a, b) swap_internal(&(a), &(b), sizeof *(1 ? &(a) : &(b)))
                                                                ^
swap.c:16:5: note: in expansion of macro ‘SWAP’
     SWAP(cat, dog);
     ^
~/c/swap $ clang swap.c
swap.c:16:5: warning: pointer type mismatch ('int *' and 'double *') [-Wpointer-type-mismatch]
    SWAP(cat, dog);
    ^~~~~~~~~~~~~~
swap.c:5:57: note: expanded from macro 'SWAP'
#define SWAP(a, b) swap_internal(&(a), &(b), sizeof *(1 ? &(a) : &(b)))
                                                        ^ ~~~~   ~~~~
1 warning generated.
~/c/swap $ icc swap.c
swap.c(16): warning #42: operand types are incompatible ("int *" and "double *")
      SWAP(cat, dog);
      ^

这个宏只计算一次所有的东西(sizeof 是特殊的,因为它不计算它的参数)。这提供了针对array[something()] 等参数的安全性。我能想到的唯一限制是它不适用于register 变量,因为它依赖于指针,但除此之外,它是通用的——你甚至可以将它用于可变长度数组。它甚至可以处理交换相同的变量——这不是你想要的。

【讨论】:

  • 这可能会破坏大多数编译器中的一些优化。许多人很擅长看穿 memcpy 的这种使用方式,但绝对不是完美的。如果您关心性能,我强烈建议您不要使用它。充其量这可能会为不支持 GNU 扩展的编译器提供 #ifdef 后备,特别是 typeof()
  • 实现swap_internal的另一种选择,虽然不是很有效,但每次交换一个字节:static inline void swap_internal(void *a, void *b, size_t size) {char *x = a;char *y = b;char t;while (size--) {t = *x; *x++ = *y;*y++ = t;}}。如果不支持 VLA 或内存紧张,它可能会很有用。
【解决方案5】:

在 C 中,这通常使用宏来完成,
有非常简单的示例,例如:
#define SWAP(type,a,b) {type _tmp=a;a=b;b=_tmp;}
...但我不建议使用它们,因为它们有一些非明显的缺陷。

这是为避免意外错误而编写的宏。

#define SWAP(type, a_, b_) \
do { \
    struct { type *a; type *b; type t; } SWAP; \
    SWAP.a  = &(a_); \
    SWAP.b  = &(b_); \
    SWAP.t  = *SWAP.a; \
    *SWAP.a = *SWAP.b; \
    *SWAP.b =  SWAP.t; \
} while (0)
  • 每个参数只实例化一次,
    所以SWAP(a[i++], b[j++]) 不会产生问题副作用。
  • 临时变量名称也是SWAP,以免在不同的名称碰巧与选择的硬编码名称发生冲突时导致错误。
  • 它没有调用memcpy(事实上,它最终在我的测试中进行了真正的函数调用,即使编译器可能会优化它们)。
  • 经过类型检查
    (与指针进行比较会使编译器在它们不匹配时发出警告)。

【讨论】:

  • 您使用的是结构而不是三个单独的变量,因为 SWAP 是唯一可以保证安全使用的名称,对吧? (所以你只能有一个变量)。另一种选择是使用_a_b_tmp 之类的东西,因为使用宏的代码不应触及任何带前缀的变量。可以在宏中隐藏一个名为 _a 的全局变量,因为宏的使用不应涉及 SWAP(_a, x)。但是,即使在使用保留名称作为自己的变量的不良程序中,您的方式也可以避免这种破坏源。
  • 对,有些项目使用-Wshadow,所以即使不太可能,也能排除这种可能性。
【解决方案6】:

检查您的编译器文档。编译器可能有一个用于交换字节的swapb 函数,并且我提供了其他类似的函数。

最坏的情况,浪费一天时间写一些通用的交换函数。它不会占用您项目的大量时间。

【讨论】:

    【解决方案7】:

    这里没有提到的另一个宏:如果您提供临时变量,则不需要提供类型。此外,逗号运算符在此处可以避免 do-while(0) 技巧。但通常我不在乎,简单地写三个命令。另一方面,如果 a 和 b 更复杂,则临时宏很有用。

    #define SWAP(a,b,t) ((t)=(a), (a)=(b), (b)=(t))
    
    void mix_the_array (....)
    {
        int tmp;
        .....
        SWAP(pointer->array[counter+17], pointer->array[counter+20], tmp);
        .....
    }
    
    #undef SWAP
    

    【讨论】:

      【解决方案8】:

      本质上,swap函数就是交换两个内存块。有了两个地址和块的大小(以字节为单位),我们可以交换指针、整数、双精度数、数组、结构......

      指针由三个部分组成,例如我们可以将short* p 分解为三部分

      1. 地址:无效* p
      2. size:在void*p处读取两个字节,我们得到一个短整数。
      3. 用法:例如用%hu 打印一个短整数

      使用前两部分,我们将能够构建一个通用的交换函数:

      #include<stdint.h> 
      #ifdef _WIN32
      #define alloca _alloca
      #else
      #include <alloca.h>
      #endif
      
      void gswap(void * const a, void * const b, int const sz) {
          // for most case, 8 bytes will be sufficient.
          int64_t tmp; // equivalent to char tmp[8];
          void * p;
          bool needfree = false;
          if (sz > sizeof(int64_t)) {
              // if sz exceed 8 bytes, we allocate memory in stack with little cost.
              p = alloca(sz);
              if (p == NULL) {
                  // if sz is too large to fit in stack, we fall back to use heap.
                  p = malloc(sz);
                  //assert(p != NULL, "not enough memory");
                  needfree = true;
              }
          }
          else {
              p = &tmp;
          }
      
          memcpy(p, b, sz);
          memcpy(b, a, sz);
          memcpy(a, p, sz);
      
          if (needfree) {
              free(p);
          }
      
      }
      

      例如:

      {// swap int 
          int a = 3;
          int b = 4;
          printf("%d,%d\n", a, b);//3,4
          gswap(&a, &b, sizeof(int));
          printf("%d,%d\n", a, b);//4,3
      }
      {// swap int64
          int64_t a = 3;
          int64_t b = 4;
          printf("%lld,%lld\n", a, b);//3,4
          gswap(&a, &b, sizeof(int64_t));
          printf("%lld,%lld\n", a, b);//4,3
      }
      {// swap arrays
          int64_t a[2] = { 3,4 };
          int64_t b[2] = { 5,6 };
          printf("%lld,%lld,%lld,%lld\n", a[0], a[1], b[0], b[1]);//3,4,5,6
          gswap(&a, &b, sizeof(a));
          printf("%lld,%lld,%lld,%lld\n", a[0], a[1], b[0], b[1]);//5,6,3,4
      }
      {// swap arrays
          double a[2] = { 3.,4. };
          double b[2] = { 5.,6. };
          printf("%lf,%lf,%lf,%lf\n", a[0], a[1], b[0], b[1]);//3.000000, 4.000000, 5.000000, 6.000000
          arrswap(&a, &b, sizeof(a));
          printf("%lf,%lf,%lf,%lf\n", a[0], a[1], b[0], b[1]);//5.000000, 6.000000, 3.000000, 4.000000
      }
      

      【讨论】:

        【解决方案9】:

        一种可能的解决方案是采用大小参数,一次只交换一个字节

        #include <stdint.h>
        #include <stdlib.h>
        
        inline void swap_generic(void* const restrict l, void* const restrict r, const size_t size) {
            uint8_t* const left  = l;
            uint8_t* const right = r;
        
            for (size_t i = 0; i != size; ++i) {
                uint8_t temp_byte = right[i];
                right[i]  = left[i];
                left[i]   = temp_byte;
            }
        }
        

        【讨论】:

          【解决方案10】:

          这是一个可以说比迄今为止提出的任何解决方案都更好的解决方案:

          #include <stddef.h>
          #include <string.h>
          
          inline void memswap(void * restrict buffer, void *l, void *r, size_t size) {
              memcpy(buffer, l, size);
              memmove(l, r, size);
              memcpy(r, buffer, size);
          }
          
          #define swap(a, b) \
              memswap(&(struct {\
                  _Static_assert(sizeof *(a) == sizeof *(b), "arguments of swap must have same size" ); \
                  char _[sizeof*(a)];\
              }) {0}, (a), (b), sizeof*(a))
          
          
          void example(int *x, int *y) {
              // produces same assembly instructions as
              //     int t = *x;
              //     *x = *y
              //     *y = t;
              swap(x, y);
          }
          

          基本思想是我们创建一个与*a 大小相同的复合文字,并将其传递给memswap 辅助函数,该函数在b 缓冲区的帮助下交换lr。 复合文字还包含一个_Static_assert 声明,它确保*a 的大小等于*b 的大小。

          这个解决方案有很多优点:

          • 由于额外的尺寸测试,它更安全
          • 与其他所有宏一样,宏扩展为表达式,而不是语句
          • 它不依赖 VLA,因此更便携
          • 它没有声明任何局部变量
          • it produces optimal code,至少对于 GCC 和 clang 而言

          【讨论】:

            【解决方案11】:

            一些交换机制涉及-

            //( Not quite good, try passing ++x and ++y as arguments :-} )
            #define SWAP_0(x,y) { x = x+y; \
                                  y = x-y; \
                                  x = x-y; }
            
            //Faster than SWAP_0
            #define SWAP_1(x,y) { x ^= y; \
                                  y ^= x; \
                                  x ^= y; }
            
            //Optimal for general usage
            #define SWAP_2(x,y)                                                                         \
                             do                                                                         \
                            {                                                                           \
                                uint8_t __temp[sizeof(x) == sizeof(y) ? (signed)sizeof(x) : -1];        \
                                memcpy(__temp,     &y, sizeof(x));                                      \
                                memcpy(    &y,     &x, sizeof(x));                                      \
                                memcpy(    &x, __temp, sizeof(x));                                      \
                            } while(0)
            
            //using GCC specific extension
            #define SWAP_3(x, y) do                                                                     \
                                {                                                                       \
                                    typeof(x) SWAP_3 = x; x = y; y = SWAP_3;                            \
                                }while (0)
            
            //without GCC specific extension - can be invoked like this - SWAP_3(x,y, int) or SWAP_3(x,y, float)
            #define SWAP_4(x, y, T) do                                                                  \
                                {                                                                       \
                                    T SWAP_4 = x; x = y; y = SWAP_4;                                    \
                                }while (0)
            

            【讨论】:

              【解决方案12】:

              以下宏以类型安全的方式执行此操作,并且适用于不同的类型

              #include <memory.h>
              
              #define SWAP(a, b) do { char tmp1[sizeof(a)], tmp2[sizeof(a)]; \
                  memcpy(tmp1, &(a), sizeof(a)); \
                  (a) = (b); \
                  memcpy(tmp2, &(a), sizeof(a)); \
                  memcpy(&(a), tmp1, sizeof(a)); \
                  (b) = (a); \
                  memcpy(&(a), tmp2, sizeof(a)); \
                  } while(0)
              

              这个宏

              1. 首先将a 的原始值存储在临时缓冲区tmp1
              2. 通过b 的赋值获得a 的新值
              3. a 的新值存储到临时缓冲区tmp2
              4. tmp1 恢复a 的原始值
              5. 通过a的赋值获得b的最终值
              6. 终于从tmp2恢复了a的正确新值

              Godbolt.org compiler explorer 表明,至少 GCC 能够在 -O2 时很好地优化这一点。

              【讨论】:

                【解决方案13】:

                其他答案中有很多不错的解决方案!我在阅读 K&R 时想到的另一个 C 快速交换宏:

                #define SWAP(TYPE,x,y) TYPE a = x, x=y, y=a
                

                【讨论】:

                  【解决方案14】:

                  如果是数值(至少):

                  我知道这不是一个实际或完整的答案,但直到现在每个人都在使用临时变量,所以我认为Chris Taylors blog 可能值得一提,它确实消除了对 typeof() 等的需要。

                  a = a ^ b;
                  b = a ^ b;
                  a = a ^ b;
                  

                  a = a + b;
                  b = a - b;
                  a = a - b;
                  

                  理论上我认为这些技术也可以应用于字符串和其他类型。

                  仍然只有三个操作。

                  【讨论】:

                  • 浮点值可能是错误的,所以不是所有的“数字”类型。第二个可能是签名类型的 UB(由于溢出)。
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2017-12-27
                  • 1970-01-01
                  • 1970-01-01
                  • 2015-08-12
                  相关资源
                  最近更新 更多