不用引入第三变量交换两个变量的值

本文探讨了不使用额外变量进行变量交换的方法,包括算术交换、位运算交换和指针交换等,对比了各种方法的优缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前几天发现了一个问题:有人告诉我,要进行变量交换,就必须引入第三变量!
假设我们要交换a和b变量的值,如果写成
int a=5,b=10;
a=b;
b=a;
那么结果就是两个都是10,理由不言而喻。
所以就应该引入第三变量,在a的值被覆盖之前就把a的值保留好。
int a=5,b=10,tmp;
tmp=a;
a=b;
b=tmp;
这样,就要引入了第三个变量,然而,我们能不能不引入第三变量来实现变量交换呢?
答案自然是肯定的,首先我们可以这样设想,如果a的值被覆盖了,那么就没法知道b应该放什么值了,
所以,我们要保留a的值,因此我们可以把a和b的值合起来,放在a里,再把合起来的值分开,分别放到b和a中:
int a=5,b=10;
a=a+b;   //a=15,b=10
b=a-b;   //a=15,b=5
a=a-b;   //a=10,b=5
但是这样做有一个缺陷,假设它运行在vc6环境中,那么int的大小是4 Bytes,所以int变量所存放的最大值是2^31-1即2147483647,如果我们令a的值为2147483000,b的值为1000000000,那么a和b相加就越界了。
事实上,从实际的运行统计上看,我们发现要交换的两个变量,是同号的概率很大,而且,他们之间相减,越界的情况也很少,因此我们可以把上面的加减法互换,这样使得程序出错的概率减少:
int a=5,b=10;
a-=b;   //a=-5,b=10
b+=a;   //a=15,b=5
a+=b;   //a=10,b=5
通过以上运算,a和b中的值就进行了交换。表面上看起来很简单,但是不容易想到,尤其是在习惯引入第三变量的算法之后。
它的原理是:把a、b看做数轴上的点,围绕两点间的距离来进行计算。
具体过程:第一句“a-=b”求出ab两点的距离,并且将其保存在a中;第二句“b+=a”求出a到原点的距离(b到原点的距离与ab两点距离之差),并且将其保存在b中;第三句“a+=b”求出b到原点的距离(a到原点距离与ab两点距离之和),并且将其保存在a中。完成交换。
此算法与引入第三变量的算法相比,多了三个计算的过程,但是没有借助临时变量,因此我们称之为算术交换算法。

因外上面的算术交换算法有导致变量溢出的危险,所以我们再想办法引入一个逻辑运算——位异或,也能得到交换效果,而且不会导致溢出。
位异或运算符是“^”,它的作用是按照每个位进行异或运算,异或运算有一个特点:
通过异或运算能够使数据中的某些位翻转,其他位不变。这就意味着任意一个数与任意一个给定的值连续异或两次,值不变。 即:a^b^b=a。将a=a^b代入b=a^b则得b=a^b^b=a;同理可以得到a=b^a^a=b;
如存在c=a^b;这种关系后,任意给出两个变量进行位异或运算,都能得到剩下的第三个变量:
a=b^c;
b=a^c;
c=a^b;
因此位异或也常用于密码学中。
因为它是按位进行运算的,因此没有溢出的情况,在这里,我们运用位异或运算来交换变量的值。
int a=10,b=12; //a=1010^b=1100;
a=a^b; //a=0110^b=1100;
b=a^b; //a=0110^b=1010;
a=a^b; //a=1100=12;b=1010;
轻松完成交换。
理论上重载“^”运算符,也可以实现任意结构的交换

另外,如果变量较大,或者交换较复杂的类,这样交换也是很慢的,因此可以使用指针交换,
因为对地址的操作实际上进行的是整数运算,比如:两个地址相减得到一个整数,表示两个变量在内存中的储存位置隔了多少个字节;地址和一个整数相加即“a+10”表示以a为基地址的在a后10个a类数据单元的地址。所以理论上可以通过和算术算法类似的运算来完成地址的交换,从而达到交换变量的目的。即:
int *a,*b;
*a=new int(10);
*b=new int(20); //&a=0x00001000h,&b=0x00001200h
a=(int*)(b-a); //&a=0x00000200h,&b=0x00001200h
b=(int*)(b-a); //&a=0x00000200h,&b=0x00001000h
a=(int*)(b+int(a)); //&a=0x00001200h,&b=0x00001000h
通过以上运算a、b的地址真的已经完成了交换,且a指向了原先b指向的值,b指向原先a指向的值了吗?上面的代码可以通过编译,但是执行结果却令人匪夷所思!原因何在?
首先必须了解,操作系统把内存分为几个区域:系统代码/数据区、应用程序代码/数据区、堆栈区、全局数据区等等。在编译源程序时,常量、全局变量等都放入全局数据区,局部变量、动态变量则放入堆栈区。这样当算法执行到“a=(int*)(b-a)”时,a的值并不是0x00000200h,而是要加上变量a所在内存区的基地址,实际的结果是:0x008f0200h,其中0x008f即为基地址,0200即为a在该内存区的位移。它是由编译器自动添加的。因此导致以后的地址计算均不正确,使得a,b指向所在区的其他内存单元。再次,地址运算不能出现负数,即当a的地址大于b的地址时,b-a<0,系统自动采用补码的形式表示负的位移,由此会产生错误,导致与前面同样的结果。
有办法解决吗?当然有,以下是改进的算法:
if(a<b)
{
a=(int*)(b-a);
b=(int*)(b-(int(a)&0x0000ffff));
a=(int*)(b+(int(a)&0x0000ffff));
}
else
{
b=(int*)(a-b);
a=(int*)(a-(int(b)&0x0000ffff));
b=(int*)(a+(int(b)&0x0000ffff));
}
算法做的最大改进就是采用位运算中的与运算“int(a)&0x0000ffff”,因为地址中高16位为段地址,后16位为位移地址,将它和0x0000ffff进行与运算后,段地址被屏蔽,只保留位移地址。这样就原始算法吻合,从而得到正确的结果。
此算法同样没有使用第三变量就完成了值的交换,与算术算法比较它显得不好理解,但是它有它的优点即在交换很大的数据类型时,它的执行速度比算术算法快。因为它交换的时地址,而变量值在内存中是没有移动过的。

以上四个算法均实现了不借助其他变量来完成两个变量值的交换,相比较而言算术算法和位算法计算量相当,地址算法中计算较复杂,却可以很轻松的实现大类型(比如自定义的类或结构)的交换,而算术算法和位算法只能进行整形数据的交换,而引用第三变量的算法无疑是最好的,能够解决任意类型的交换问题。
<think>好的,我需要帮助用户找到在使用临时变量的情况下交换两个的方法。用户提到了位运算和加减法,我应该详细解释这两种方法,并提供示例代码。首先,我要回忆一下这两种方法的原理和实现步骤。 对于加减法,我记得可以通过将两个数的和存储在一个变量中,然后通过减法来交换。比如,假设a和b是要交换的数,可以这样做:a = a + b,然后b = a - b,这样就得到了原来的a的,接着a = a - b,得到原来的b的。但需要注意整数溢出问题,特别是在编程语言中,如果数过大可能会导致溢出错误。 然后是位运算,使用异或操作。异或的性质是相同为0,同为1。所以,可以通过三次异或操作来交换两个数。例如:a = a ^ b,b = a ^ b(此时b变为原来的a),a = a ^ b(此时a变为原来的b)。这种方法需要担心溢出,但只能用于整数类型。 接下来,我需要验证这些方法的正确性,并考虑它们在同编程语言中的适用性。例如,在C、Java或Python中,这些方法是否可行?比如,Python中的整数可以很大,使用加减法可能会有溢出问题,但在其他语言如C中,如果a和b的和超过了数据类型的最大,就会溢出,导致错误。因此,在示例代码中需要提醒用户注意这些限制。 另外,用户提供的引用中提到了C++中使用中间变量的方法,以及通过指针传递来交换。虽然用户现在需要中间变量,但可能需要说明这些替代方法的优缺点。例如,使用中间变量的方法虽然节省了空间,但可能影响可读性或存在潜在风险,如溢出或仅限于某些数据类型。 在编写示例代码时,应该使用用户提到的语言,如C语言,因为引用中有C的示例。例如,加减法的C代码示例: int a = 5, b = 3; a = a + b; b = a - b; a = a - b; 同样,位运算的示例: int a = 5, b = 3; a = a ^ b; b = a ^ b; a = a ^ b; 需要确保这些代码的正确性,并指出可能的错误情况。例如,如果a和b是同一个变量,位运算方法会导致两者都变为0,因此需要检查a和b是否相同。 最后,总结这些方法的适用场景和注意事项,帮助用户根据具体情况选择最合适的方法。</think>在使用临时变量的情况下交换两个,可通过以下两种常用方法实现: --- ### 一、加减法交换 **原理**:通过加减运算调整数,适用于整数类型,但需注意**整数溢出风险**。 **步骤**: 1. 将两数之和赋给第一个变量:$a = a + b$ 2. 用总和减去原第二个变量得到原第一个变量,赋给第二个变量:$b = a - b$ 3. 用总和减去新的第二个变量(原第一个变量)得到原第二个变量,赋给第一个变量:$a = a - b$ **C语言示例**: ```c int a = 5, b = 3; a = a + b; // a=8, b=3 b = a - b; // a=8, b=5(原a的) a = a - b; // a=3, b=5(交换完成) ``` --- ### 二、位运算交换(异或操作) **原理**:利用异或运算的自反性($x \oplus x = 0$ 和 $x \oplus 0 = x$),仅适用于整数类型,无溢出风险[^4]。 **步骤**: 1. 将异或结果赋给第一个变量:$a = a \oplus b$ 2. 用异或结果与原第二个变量异或得到原第一个变量,赋给第二个变量:$b = a \oplus b$ 3. 用异或结果与新第二个变量异或得到原第二个变量,赋给第一个变量:$a = a \oplus b$ **C语言示例**: ```c int a = 5, b = 3; a = a ^ b; // a=6, b=3(二进制 110) b = a ^ b; // a=6, b=5(原a的) a = a ^ b; // a=3, b=5(交换完成) ``` --- ### 注意事项 1. **适用性**: - 加减法可能导致溢出(如 $a + b$ 超过数据类型范围)[^2]。 - 位运算适用于浮点数或非数类型。 2. **可读性**:这类方法可能降低代码可读性,建议在性能敏感场景使用。 3. **变量相同性**:若两变量指向同一内存地址,位运算会将其置零。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值