不使用第三个变量,交换两个变量的值

本文深入探讨了不借助临时变量实现变量交换的多种算法,包括算术运算、指针地址操作、位运算及栈实现。重点介绍了算术算法、地址算法和位运算算法,它们分别在不同场景下展现出各自的优缺点。通过对比分析,揭示了这些算法在实际应用中的考量因素,旨在为读者提供全面的技术视角。

原文出处: http://blog.163.com/jian_xiong/blog/static/19193236320130604848997/


通常我们的做法是(尤其是在学习阶段):定义一个新的变量,借助它完成交换。代码如下:
int a,b;
a=10; b=15;
int t;
t=a; a=b; b=t;
这种算法易于理解,特别适合帮助初学者了解计算机程序的特点,是赋值语句的经典应用。在实际软件开发当中,此算法简单明了,不会产生歧义,便于程序员之间的交流,一般情况下碰到交换变量值的问题,都应采用此算法(以下称为标准算法)。

上面的算法最大的缺点就是需要借助一个临时变量。那么不借助临时变量可以实现交换吗?答案是肯定的!这里我们可以用三种算法来实现:1)算术运算;2)指针地址操作;3)位运算;4)栈实现。

1) 算术运算
int a,b;
a=10;b=12;
a=b-a; //a=2;b=12
b=b-a; //a=2;b=10
a=b+a; //a=10;b=10
它的原理是:把a、b看做数轴上的点,围绕两点间的距离来进行计算。
具体过程:第一句“a=b-a”求出ab两点的距离,并且将其保存在a中;第二句“b=b-a”求出a到原点的距离(b到原点的距离与ab两点距离之差),并且将其保存在b中;第三句“a=b+a”求出b到原点的距离(a到原点距离与ab两点距离之和),并且将其保存在a中。完成交换。
此算法与标准算法相比,多了三个计算的过程,但是没有借助临时变量。(以下称为算术算法)
缺点:是只能用于数字类型,字符串之类的就不可以了。a+b有可能溢出(超出int的范围),溢出是相对的, +了溢出了,-回来不就好了,所以溢出不溢出没关系,就是不安全。

2) 指针地址操作
因为对地址的操作实际上进行的是整数运算,比如:两个地址相减得到一个整数,表示两个变量在内存中的储存位置隔了多少个字节;地址和一个整数相加即“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进行与运算后,段地址被屏蔽,只保留位移地址。这样就原始算法吻合,从而得到正确的结果。
此算法同样没有使用第三变量就完成了值的交换,与算术算法比较它显得不好理解,但是它有它的优点即在交换很大的数据类型时,它的执行速度比算术算法快。因为它交换的时地址,而变量值在内存中是没有移动过的。(以下称为地址算法)

3) 位运算
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;
此算法能够实现是由异或运算的特点决定的,通过异或运算能够使数据中的某些位翻转,其他位不变。这就意味着任意一个数与任意一个给定的值连续异或两次,值不变。
 
4)栈实现。不多解释了,栈和相关函数定义省去。
int exchange(int x,int y)
{
 stack S;
 push(S,x);
 push(S,y);
 x=pop(S);
 y=pop(S);
}

以上算法均实现了不借助其他变量来完成两个变量值的交换,相比较而言算术算法和位算法计算量相当,地址算法中计算较复杂,却可以很轻松的实现大类型(比如自定义的类或结构)的交换,而前两种只能进行整形数据的交换(理论上重载“^”运算符,也可以实现任意结构的交换)。
介绍这三种算法并不是要应用到实践当中,而是为了探讨技术,展示程序设计的魅力。从中可以看出,数学中的小技巧对程序设计而言具有相当的影响力,运用得当会有意想不到的神奇效果。而从实际的软件开发看,标准算法无疑是最好的,能够解决任意类型的交换问题。

<think>好的,我现在需要帮助用户解决在C++中使用第三个变量交换两个变量的问题。根据用户提供的引用资料,我需要整理出几种同的方法,并确保解释清晰正确。 首先,用户引用了四种方法,其中方法一是指针方式,方法二是算术运算,方法三是位运算,还有一种是使用异或操作。过看起来这些引用中有重复的内容,比如方法二和方法四都是使用异或,可能用户希望区分同的方法。 我需要先仔细阅读每个引用内容。引用1提到传指针的方式,但用户的问题是使用临时变量,所以可能指针方法虽然涉及指针,但依然使用第三个变量。引用2展示了通过指针进行算术运算来交换,即a = a + b,然后b = a - b,最后a = a - b。引用3和4则展示了使用异或运算的方法,比如a = a ^ b,然后依次更新b和a。 接下来,我需要确认这些方法是否都符合用户的需求:使用临时变量。看起来是的,因为它们都没有引入第三个变量作为中间存储。 然后,我需要将这些方法整理成同的方法,可能包括算术方法、异或方法,以及可能的指针操作。但指针方法虽然使用了指针变量,但这些指针本身并是用来存储的,所以可能也算符合条件。过用户可能更希望借助任何额外的变量,包括指针变量,因此可能需要区分。 过根据引用2中的示例,虽然使用了指针变量ptrA和ptrB,但实际交换时是通过算术运算操作,而指针变量只是用来获取地址,可能使用第三个变量来存储。但用户的问题是使用临时变量,所以ptrA和ptrB其实也是变量,虽然它们存储的是地址而,但严格来说可能符合用户的要求。因此,可能应该排除指针方法,或者明确指出这种方法是否需要额外的变量。 但引用2中的示例其实是通过指针来访问变量,然后使用算术运算进行交换,而ptrA和ptrB是局部指针变量,但用户的问题可能是指使用第三个变量来存储被交换,而指针变量可能被视为临时变量。所以可能需要明确说明哪些方法需要额外的变量,哪些需要。 根据用户的问题,他们希望使用临时变量,所以应该选择那些需要任何额外变量的方法,包括指针变量。例如,引用2中的代码虽然用了指针变量ptrA和ptrB,但用户可能希望避免这种情况,所以更好的方法是直接操作变量本身,而需要任何中间指针。 因此,正确的方法应该是算术运算和异或运算,这两种方法都需要任何临时变量,无论是变量还是指针变量。而传指针的方法可能涉及指针变量,但用户可能更倾向于需要任何辅助变量的方法。 现在需要整理出两种或三种方法: 1. 算术运算法:通过加减法来实现交换2. 异或运算法:通过位运算异或来实现交换。 3. 可能还有使用乘除法的方法,但需要注意溢出问题,可能推荐。 根据引用中的内容,用户提供的示例中有两种方法:算术和异或。因此,我应该主要介绍这两种方法。 接下来,我需要确保代码的正确性,并解释每一步的操作,同时指出潜在的问题,比如溢出或数据类型限制。 例如,算术方法在数较大时可能导致溢出,而异或方法只适用于整数类型。这些注意事项需要提及,以帮助用户正确选择方法。 同时,用户可能在之前的对话中提到了这些方法,所以需要确认是否有其他方法,但根据引用资料,这里主要两种方法。 然后,我需要按照用户的示例结构来组织回答,包括分点说明每种方法,给出代码示例,以及注意事项。同时,生成相关问题。 最后,确保使用中文回答,数学表达式正确使用$...$,独立公式用$$...$$,但在这个问题中可能没有数学公式,但如果有的话需要处理。 现在开始组织回答: 首先,分点介绍两种方法:算术运算和异或运算。每种方法给出步骤、示例代码和注意事项。 然后,注意事项部分说明两种方法的局限性和适用场景。 最后,生成相关问题,如比较同方法、适用场景、潜在问题等。</think>在C++中实现**借助第三个变量**交换两个变量,主要有以下两种经典方法。两种方法均通过数学运算或位运算实现,但需注意数据类型和运算范围的限制[^2][^4]。 --- ### 方法一:算术运算法 通过加减法操作实现变量交换,步骤如下: 1. 将两个变量的和存入第一个变量:$a = a + b$ 2. 用总和减去原始$b$得到原始$a$,存入$b$:$b = a - b$ 3. 用总和减去新的$b$(即原始$a$)得到原始$b$,存入$a$:$a = a - b$ **示例代码**: ```cpp #include <iostream> int main() { int a = 10, b = 20; std::cout << "交换前: a=" << a << ", b=" << b << std::endl; a = a + b; // a存储两数之和 b = a - b; // b变为原始a的 a = a - b; // a变为原始b的 std::cout << "交换后: a=" << a << ", b=" << b << std::endl; return 0; } ``` **注意事项**: - 仅适用于**数类型**(如`int`, `float`) - 可能发生**整数溢出**(如$a + b$超过类型最大时)[^2] --- ### 方法二:异或运算法 利用异或运算(XOR)的以下性质实现交换: 1. 交换律:$a \oplus b = b \oplus a$ 2. 结合律:$(a \oplus b) \oplus c = a \oplus (b \oplus c)$ 3. 自反性:$a \oplus a = 0$,$a \oplus 0 = a$ **步骤**: 1. $a = a \oplus b$(存储差异信息) 2. $b = a \oplus b$(通过差异还原原始$a$) 3. $a = a \oplus b$(通过差异还原原始$b$) **示例代码**: ```cpp #include <iostream> int main() { int x = 3, y = 5; std::cout << "交换前: x=" << x << ", y=" << y << std::endl; x = x ^ y; // x存储差异信息 y = x ^ y; // y还原为原始x的 x = x ^ y; // x还原为原始y的 std::cout << "交换后: x=" << x << ", y=" << y << std::endl; return 0; } ``` **注意事项**: - 仅适用于**整数类型**(如`int`, `char`) - 可用于浮点数或自定义对象[^4] --- ### 对比与建议 | 方法 | 优点 | 缺点 | 适用场景 | |------------|------------------------|--------------------------|------------------| | 算术运算 | 代码直观 | 可能溢出,仅限数类型 | 小整数或浮点数 | | 异或运算 | 无溢出风险,效率高 | 仅限整数,可读性较差 | 整型数据交换 | 实际开发中推荐直接使用`std::swap`,其内部通过移动语义实现高效交换[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值