C语言中是否有内置的交换函数?

40

在C语言中,是否有不需要使用第三个变量就可以进行交换的内置交换函数?

10个回答

32

4
应避免使用求和和差的解决方案。对带符号类型的操作数执行此类操作可能会导致溢出,而在 C 语言中,有符号溢出是未定义的。 - ouah
C++中的交换并不像 c=a; a=b; b=c; 这样工作。 - einpoklum
不要使用异或(XOR)操作符,也不要试图避免第三个变量。编译器比你更懂得如何完成它的工作。 - einpoklum

29
为什么你不想使用第三个变量?在绝大多数体系结构上,这是最快的方法。 XOR交换算法可以在不使用第三个变量的情况下运行,但它有两个问题:
1. 变量必须是不同的,即swap(&a, &a)将无法工作。 2. 总的来说,速度较慢。
如果使用第三个变量会导致堆栈溢出,则有时可能更喜欢使用XOR交换,但通常您不处于这种决策位置。
直接回答您的问题,标准C中没有swap函数,尽管编写它非常简单。

@delnan:我并不是想争论性能的重要性。我的意思是,没有理由(包括性能)不想要第三个变量。 - Peter Alexander
9
@delnan提出了两种交换变量的方法,第一种是使用中间变量t,第二种是使用异或操作。这两种方法的效率都很高,因此不存在时间浪费的问题。写一个交换变量的函数不会显著地影响解决问题所需的时间。这应该是我见过的最不重要的问题了。 - Peter Alexander
如果您查看我的帖子,我相信我的函数被认为是类型不可知的,除非您必须在第三个参数中指定要交换的值的字节数,因此我猜这不算是“不使用第三个变量”。 - Patrick Roberts
XOR Swap 主要是针对嵌入式系统中内存宝贵的 C 程序员的一个技巧。 - A. Gille

10
假设您需要一个 C 解决方案,而不是 C++ 解决方案,则可以将其制作为宏,在使用 GCC 扩展使其足够通用时,类似于:
 #define SWAP(x,y) do {   \ 
   typeof(x) _x = x;      \
   typeof(y) _y = y;      \
   x = _y;                \
   y = _x;                \
 } while(0)

小心一些像调用 swap(t[i++],i) 这样的技巧;为了避免它们,使用地址运算符&。你最好使用临时变量(对于整数,有一个著名而无用的异或技巧)。
注:我使用两个本地变量 _x_y(但我可以只使用一个本地变量)以便更好地阅读,并且也许可以使编译器进行更多优化。

2
使用两个临时变量而不是一个的好处是什么(比如说,typeof(x) _tmp = x; x = y; y = _tmp;)? - einpoklum
1
只是更好的可读性,也许更容易被编译器优化。 - Basile Starynkevitch

8

标准C语言中没有这样的函数。

(在C++中,你可以使用std::swap()函数。)


也许这个问题中的宏定义对你有用。


7

在C语言中,没有标准的函数可以交换两个变量。

可以编写以下宏来实现:

#define SWAP(T, a, b) do { T tmp = a; a = b; b = tmp; } while (0)

宏可以这样调用:

int a = 42;
int b = 2718;

SWAP(int, a, b);

有一些编写SWAP宏的解决方案应该避免使用:

#define SWAP(a, b) do { a = b + a; b = a - b; a = a - b; } while (0)

当操作数为有符号类型时,可能会发生溢出,有符号溢出是未定义的行为。

此外,应避免尝试优化XOR解决方案的解决方案:

#define SWAP(a, b) (a ^= b ^= a ^=b)

a 在前一个序列点和下一个序列点之间被修改了两次,因此它违反了序列点规则且是未定义的行为。


@BasileStarynkevitch的版本的SWAP宏不是更好吗? - einpoklum
1
@einpoklum:不行,因为并非每个编译器都是GCC,所以您不能依赖于像typeof(expr)这样的GCC扩展。 - user539810
1
我特别喜欢在宏的第一个参数中使用 int。现在我们可以说我们的所爱语言中有模板了 :-) - paxdiablo
请考虑删除XOR-swap建议。 - einpoklum

4

在C语言中,你可以将任何对象的表示形式复制到一个无符号字符数组中。以下宏允许您交换任何两个对象:

#define SWAP(X,Y) \
    do { \
        unsigned char _buf[sizeof(*(X))]; \
        memmove(_buf, (X), sizeof(_buf)); \
        memmove((X),  (Y), sizeof(_buf)); \
        memmove((Y), _buf, sizeof(_buf)); \
    } while (0)

GCC在某些情况下甚至会为此生成最优代码。然而你可能会失去你的工作...


任意两个对象?比如... int x; double y; ? :-) 或许你的意思是同一类型的任意两个对象。 - paxdiablo
我很好奇,为什么我不能保住我的工作? - foragerDev
@foragerDev:如果你不得不问,那么肯定无法保住你的工作 :-P - einpoklum
@einpoklum 但如果您不介意告诉我,也许我可以保住我的工作。 :) - foragerDev

1

有一个C++库函数,它可以交换两个整数变量的值。例如,swap(x, y); 将交换变量x和y的值。类似地,swap(mat[i][j], mat[j][i]); 将交换矩阵mat中的两个值,即第i行第j列的值和第j行第i列的值。


1
一个C++库函数既不是C函数,也不是内置函数。 - Chris

1
#define swap(T, x, y) \
    {                 \
        T tmp = x;    \
        x = y;        \
        y = tmp;      \
    }

int main()
{
    int a = 10;
    int b = 20;
    printf("a=%d b=%d\n", a, b);
    swap(int, a, b);
    printf("a=%d b=%d\n", a, b);

    return 0;
}

-1

虽然没有内置的交换函数,但你可以尝试以下方法:

a = a ^ b;

b = a ^ b;

a = b ^ a;


-2

我相信我已经想出了一种类型无关的函数,可以在标准C中交换任何两个值,尽管由于我对这种语言还比较新,可能会有所忽略。它使用异或交换算法,我确信它可以进行更多优化,但只要这两个值指向由第三个参数指定的相同字节数,它就能正常工作:

void swapn(void *a, void *b, size_t n) {
    if (a == b) {
        return;
    }

    size_t i;
    char *x = (char *)a,
        *y = (char *)b;

    for (i = 0; i < n; i++) {
        *x ^= *y;
        *y ^= *x;
        *x ^= *y;
        x++;
        y++;
    }
}

使用示例:

// swap two integers
int x = 5,
    y = 30;

printf("%d\t%d\n", x, y);

swapn(&x, &y, sizeof(int));

printf("%d\t%d\n\n", x, y);

// swap two floats
float a = 9.23f,
    b = 6.83f;

printf("%.2f\t%.2f\n", a, b);

swapn(&a, &b, sizeof(float));

printf("%.2f\t%.2f\n\n", a, b);

// swap two doubles
double p = 4.7539,
    q = 0.9841;

printf("%.4f\t%.4f\n", p, q);

swapn(&p, &q, sizeof(double));

printf("%.4f\t%.4f\n\n", p, q);

// swap two chars
char m = 'M',
    n = 'n';

printf("%c\t%c\n", m, n);

swapn(&m, &n, sizeof(char));

printf("%c\t%c\n\n", m, n);

// swap two strings of equivalent length
char s[] = "Hello",
    t[] = "World";

printf("%s\t%s\n", s, t);

swapn(s, t, sizeof(s));

printf("%s\t%s\n\n", s, t);

输出结果为:

5   30
30  5

9.23    6.83
6.83    9.23

4.7539  0.9841
0.9841  4.7539

M   n
n   M

Hello   World
World   Hello

我非常确定三路异或赋值是未定义行为,因为它在没有序列点的情况下两次分配给 *x。这很容易修复,但我不明白为什么要费心使用异或交换。它既不更快,也不更容易,在两个参数别名时会出错,在这个二十行的通用实现中,额外的变量甚至不是真正的更多代码。但它似乎相当类型不可知,我得承认这一点。n 应该是 size_t - user395760
@delnan,我已经移除了未定义的行为,并将第三个参数更改为size_t类型。您所说的“两个参数别名”是什么意思?我认为这已经通过最初的if语句处理了。 - Patrick Roberts

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接