求砖拍:绝对深入剖析各种方法实现两个变量的交换

来源:互联网 发布:vb时间触发事件 编辑:程序博客网 时间:2024/06/06 08:49

本篇摘要

  交换两个变量是非常古老的话题了,然而本文绝对保证给你新鲜的感觉!本文涉及到最简单的“不用临时变量交换两个整数”还涉及到如果利用异或来实现两个指针、两个浮点数的交换,要知道指针的浮点数是不允许直接异或运算的哦;同时本文还阐述了如何交换用户自定义类型及其指针。

本文完全是个人自由发挥之作,欢迎广大砖家来拍砖,我个人感觉文中必然有很多不足,甚至错误之处,这并非我谦虚,事实上我写本文的目的就是希望在挨砖板中成长!新人或许看不懂很多东西,如果你看不懂,那么就不要随便膜拜,因为看不懂的或许原本就是错的,高手看完后如果本文写得还可以,那么请留下好评,以供新手参考本文是否有阅读本文的必要,如果觉得本文完全是垃圾,那么请不要客气,您可以赤裸地,露骨地指出本文的错误和不足,让本人在批评中进步,但请不要进行人身攻击,谢谢!

准备工作

  由于本文涉及到交换两个用户自定义类型的变量,为了举例方便,本文定义如下的Person类(其中省略了拷贝构造函数的重写,因为本文不用到它):

[cpp] view plaincopyprint?
  1. class Person  
  2. {  
  3. public:  
  4.          Person(int age ,const char* name):m_Age(age)  
  5.          {  
  6.                    int len = strlen(name);  
  7.                    this->m_Name = newchar[len+1];  
  8.                    strcpy(this->m_Name,name);  
  9.          }  
  10.          Person()  
  11.          {  
  12.                    this->m_Age = -1;  
  13.                    this->m_Name = 0;  
  14.          }  
  15.          void PrintSelf()  
  16.          {  
  17.                    cout<<this->m_Name<<":"<<this->m_Age<<endl;  
  18.          }  
  19.          Person& operator= (constPerson& other)  
  20.          {  
  21.                    if (this == &other)  
  22.                    {  
  23.                             return *this;  
  24.                    }  
  25.                    else  
  26.                    {  
  27.                             this->m_Age =other.m_Age;  
  28.                             deletethis->m_Name;  
  29.                             int len =strlen(other.m_Name);  
  30.                             this->m_Name =new char[len+1];  
  31.                             strcpy(this->m_Name,other.m_Name);  
  32.                             return *this;  
  33.                    }  
  34.          }  
  35.          ~Person()  
  36.          {  
  37.                    delete this->m_Name;  
  38.          }  
  39. private:  
  40.          int m_Age;  
  41.          char* m_Name;  
  42. };  
class Person{public:         Person(int age ,const char* name):m_Age(age)         {                   int len = strlen(name);                   this->m_Name = newchar[len+1];                   strcpy(this->m_Name,name);         }         Person()         {                   this->m_Age = -1;                   this->m_Name = 0;         }         void PrintSelf()         {                   cout<<this->m_Name<<":"<<this->m_Age<<endl;         }         Person& operator= (constPerson& other)         {                   if (this == &other)                   {                            return *this;                   }                   else                   {                            this->m_Age =other.m_Age;                            deletethis->m_Name;                            int len =strlen(other.m_Name);                            this->m_Name =new char[len+1];                            strcpy(this->m_Name,other.m_Name);                            return *this;                   }         }         ~Person()         {                   delete this->m_Name;         }private:         int m_Age;         char* m_Name;};

  为了后文表述方便,这里再定义Person类的两个对象和两个指针,定义如下:

[cpp] view plaincopyprint?
  1. PersonyoungMan(18,” young man”);  
  2. Person oldMan(81,”old man”);  
  3. Person* pYoungMan= &youngMan;  
  4. Person* pOldMan= &oldMan;  
PersonyoungMan(18,” young man”);Person oldMan(81,”old man”);Person* pYoungMan= &youngMan;Person* pOldMan= &oldMan;

最常见的交换两个对象的方法:GeneralSwap

   通常,我们为了交换两个变量都采取下面的方法来实现,它需要一个临时变量:

[cpp] view plaincopyprint?
  1. template<class T>  
  2. void GeneralSwap(T& a,T& b)  
  3. {  
  4.          T temp;  
  5.          temp = a;  
  6.          a = b;  
  7.          b = temp;  
  8. }  
template<class T>void GeneralSwap(T& a,T& b){         T temp;         temp = a;         a = b;         b = temp;}

   显然人人都知道这个写法,但是我仍然觉得有必要重点申明几点:1、注意函数的参数是引用(也可指针),为什么我就不解释了;2、这个交换函数基本上是最简单、最通用的,简单到人人都会写,通用到它几乎可适用于任何数据类型:char ,int , long , float, double等各种系统自定义数学类型(无符号的,带符号的),用户自定义数据类型(需要有默认构造函数,否则语句T temp;会报错),以及各种指针(系统自定义类型的指针,和用户自定义类型的指针)。当然用户自定义类型中如果包含了指针数据成员,那么需要重载赋值运算符,事实上这样的用户自定义类,你都应该自己重写赋值运算符、拷贝构造函数,否则不但不能使用GeneralSwap,其他涉及到拷贝和赋值的操作都可能导致出错!

利用GeneralSwap交换两个用户自定义对象

  下面深入探讨一下关于用户自定义对象的交换问题:针对准备工作中的Person类的两个对象youngMan和oldMan语句GeneralSwap(youngMan,oldMan);能实现他们的交换。短短一行代码就能实现将一个18岁的花季少男跟一个81岁的老头子掉包,这像不像是耍魔术啊,呵呵。要注意了,该交换代码虽短,但涉及到默认构造函数的调用(GeneralSwap中的T temp;语句)和赋值运算符重载函数的调用(GeneralSwap中的三个赋值语句)。

或许您很少这么用吧,事实上在我写本文之前,我都没真正交换过两个自定义的对象,通常我们都不愿意这么交换两个自定义对象。原因是效率太低!或许你要问,万一有的应用就是需要交换两个自定义的对象怎么办?好办,用指针啊!对,指针的好处就是效率高,为什么C++比java效率高,原因之一就是java取消了指针。下面的第一行代码就是交换两个Person类的指针:

[cpp] view plaincopyprint?
  1. GeneralSwap(pYoungMan,pOldMan);  
  2. //GeneralSwap(*pYoungMan,*pOldMan);     //效率低  
GeneralSwap(pYoungMan,pOldMan);//GeneralSwap(*pYoungMan,*pOldMan);     //效率低

  为什么使用指针就效率高了呢?原因是指针就是地址,地址就是整数,于是问题等价于交换两个整数,因此它不调用赋值运算符重载函数!只要你在应用程序中始终通过指向对象的指针来访问对象,那么交换两个指针就能达到交换对象的目的。注意被注释掉的第二行代码,它是正确的,但是它又回到了交换两个实际对象,其效率低,最好不要这么用!

   对于这个最常见、最简单的GeneralSwap我都废话了一大堆,而且还扯出了一个没多少用的关于用户自定义对象的交换问题,这实属个人思维散射,请砖家们狠狠地拍。

在进行下一个方法之前,再次强调一点,这个方法的特点是简单、通用!后面的方法都将与之做比较。

利用加减法实现两个数的交换

   几乎人人都知道还可以利用加减法来实现两个数的交换,其代码也异常简单:

[cpp] view plaincopyprint?
  1. template<classT>  
  2. voidAdd_Sub_Swap_1(T& a, T& b)  
  3. {  
  4.         a= a+b;  
  5.         b= a-b;  
  6.          a = a-b;  
  7. }  
template<classT>voidAdd_Sub_Swap_1(T& a, T& b){        a= a+b;        b= a-b;         a = a-b;}

  Add_Sub_Swap_1可以用于交换两个整数,但由于涉及加减法,因此有数据溢出的危险;也可以用于交换浮点数,但是有可能由于舍入误差导致结果不准确。

  Add_Sub_Swap_1不能用于交换两个用户自定义的对象,下面的语句编译就通过不,编译器告诉你Person类没有定义operator +等符号:

Add_Sub_Swap_1(youngMan,oldMan);//编译通不过!

  Add_Sub_Swap_1不能用于交换两个指针,语句Add_Sub_Swap_1(pYoungMan,pOldMan);编译时将报错:error C2110:cannot add two pointers,是的,两个指针不能直接做加法运算(减法是可以的)。那么是不是就不能利用加减法实现两个指针的交换呢?答案是:“可以!”,接下来我将阐述如何实现。

利用加减法交换两个指针

  Add_Sub_Swap_1不能用于交换两个指针,前面我说可以用加减法来实现两个指针的交换,这是有根据的:指针仍然是变量,只不过它是存储普通变量的地址的变量。只要我们把指针“看作”变量,那么就能实现加法。那么如何把指针“看作”变量呢?答案是:“通过强制类型转换”!指针表示变量的地址,在32位平台上它是一个无符号的整数,因此可以将指针强制转换为无符号类型的整数。我对上面的Add_Sub_Swap_1进行了改进:

[cpp] view plaincopyprint?
  1. template<classT>  
  2. voidAdd_Sub_Swap_2(T& a, T& b)  
  3. {  
  4.          *(( unsigned*)(&a)) = *(( unsigned*)(&a))+ *(( unsigned*)(&b));  
  5.          *(( unsigned*)(&b)) = *(( unsigned*)(&a))- *(( unsigned*)(&b));  
  6.          *(( unsigned*)(&a)) = *(( unsigned*)(&a))- *(( unsigned*)(&b));  
  7. }  
template<classT>voidAdd_Sub_Swap_2(T& a, T& b){         *(( unsigned*)(&a)) = *(( unsigned*)(&a))+ *(( unsigned*)(&b));         *(( unsigned*)(&b)) = *(( unsigned*)(&a))- *(( unsigned*)(&b));         *(( unsigned*)(&a)) = *(( unsigned*)(&a))- *(( unsigned*)(&b));}

  利用Add_Sub_Swap_2既可以交换两个普通的整数、浮点数同时它可以交换两个任意类型的指针(包含系统预定义类型和用户自定义类型的指针,其实本质上所有指针都属于同一种类型:32位无符号整数类型)。不信您试试Add_Sub_Swap_2(pYoungMan,pOldMan);它能得到正确答案。

  虽然Add_Sub_Swap_2解决了Add_Sub_Swap_1无法交换两个指针的问题,但是它仍然无法交换两个用户自定义类型的变量,原因是用户自定义类型没有加减法运算。看来要想用加减法实现两个用户定义类型的交换是不可能的了(除非用户自定义的operator+和operator-能满足交换两个对象的目的,这很难,除非是非常简单的用户自定义类型,比如你不使用系统类型int非要定义一个MyInt类)。

利用异或实现两个整数的交换

同样地,几乎人人都知道利用异或来交换两个数,其实现也非常简单:

[cpp] view plaincopyprint?
  1. template <class T>  
  2. void Xor_Swap_1(T& a,T& b)  
  3. {  
  4.          a = a^b;  
  5.          b = a^b;  
  6.          a = a^b;  
  7. }  
template <class T>void Xor_Swap_1(T& a,T& b){         a = a^b;         b = a^b;         a = a^b;}

  上面的函数的实用性非常有限,它只能交换两个整数(包含char,int,long),要想交换两个浮点数是不行的,因为浮点数不能参与位运算,要想交换两个指针也是不行的,编译器不允许你把两个指针拿来做位运算,要想交换两个用户自定义对象也是不行的,因为它仍然不能参与位运算。那么是不是利用异或交换两个变量就没法用于浮点数、指针和用户自定义的对象了呢?答案是“能”!后面几节我将阐述这些问题。

利用异或实现两个float和指针的交换

  前面的Xor_Swap_1无法实现两个浮点数和指针的交换,其原因是浮点数和指针均不直接支持位运算。那么如何才能利用异或来交换两个浮点数和指针呢?方法仍然是“强制类型转换”!因为浮点数在内存中仍然是用一串二进制bit来表示的嘛,只要把浮点数看作(强制类型转换)二进制bit构成的整数,那么就能进行位运算了,至于指针嘛,处理方法完全相同。具体如何做呢,其实现大概是这样的:

[cpp] view plaincopyprint?
  1. template <class T>  
  2. void Xor_Swap_2(T& a,T& b)  
  3. {  
  4.          *((unsigned*)(&a))= *((unsigned*)(&a)) ^ *((unsigned*)(&b));  
  5.          *((unsigned*)(&b))= *((unsigned*)(&a)) ^ *((unsigned*)(&b));  
  6.          *((unsigned*)(&a))= *((unsigned*)(&a)) ^ *((unsigned*)(&b));  
  7. }  
template <class T>void Xor_Swap_2(T& a,T& b){         *((unsigned*)(&a))= *((unsigned*)(&a)) ^ *((unsigned*)(&b));         *((unsigned*)(&b))= *((unsigned*)(&a)) ^ *((unsigned*)(&b));         *((unsigned*)(&a))= *((unsigned*)(&a)) ^ *((unsigned*)(&b));}

    利用这个函数可以交换两个float类型的变量,也可以交换任意类型的指针!非常值得注意的是:用它交换两个double类型数据或者两个Person类的对象(youngMan,oldMan)均能编译通过,但是其结果却是错的。至于为什么,以及如何解决,这将是我下一节要阐述的内容。

利用异或实现两个double类型变量和用户自定义变量的交换

   Xor_Swap_2解决了利用异或不能交换两个float数据和指针的问题,然而它却不能正确地交换两个double数据和两个Person类对象。这是为什么呢?原因是函数内部是把参数强制类型转换成unsigned类型的,而sizeof(float)和sizeof(pointor)的值都等于sizeof(unsigned),但是sizeof(double)却不等于sizeof(unsigned),也就是说把double强制转换成unsigned类型时,发生了“位截断”(在概念是区别与数据截断),那么得到的结果肯定就不对了。至于无法交换两个Person类对象,其原因也相同。

  这里我要深入分析一下强制类型转换是如何发生位截断的,首先看看以下测试的输出结果,注意代码中的注释,为了节约篇幅,我把值得注意的地方都放在注释中了:

[html] view plaincopyprint?
  1. double a = 1.0,b=2.0;  
  2. Xor_Swap_2(a,b);//交换两个double数据  
  3. Cout<<a<<b;//输出仍然是1.0和2.0,a,b的值并未改变  
  4. Xor_Swap_2(youngMan,oldMan);//交换两个用户自定义对象  
  5. youngMan.PrintSelf();//输出young man:81  
  6. oldMan.PrintSelf();//输出old man:18  
double a = 1.0,b=2.0;Xor_Swap_2(a,b);//交换两个double数据Cout<<a<<b;//输出仍然是1.0和2.0,a,b的值并未改变Xor_Swap_2(youngMan,oldMan);//交换两个用户自定义对象youngMan.PrintSelf();//输出young man:81oldMan.PrintSelf();//输出old man:18

   可以看出两个double数据并没被交换,而两个Person对象在交换之后发生了怪异现象:产生了81岁的年轻人和18岁的老年人!这一点正好说明强制类型转换时发生了位截断,由于Person类的第一个数据成员m_Age正好是int型,在Xor_Swap_2内部做强制类型转换时正好取得了两个对象的m_Age成员,于是出现了两个对象被部分交换的情况,那么又如何解释两个double数据没有变法呢?事实上两个double数据仍然发生了部分交换,因为这里的两个double数(a,b)的前4个字节正好相同,因此看不出部分交换。

既然我们知道了Xor_Swap_2为什么不能用于交换两个double类型的数据和两个用户自定义的数据,那么就有办法对它进行改进。具体改进的思想就是把参数按照一个byte一个byte地分别异或,按照这个思路我实现了如下的函数:

[cpp] view plaincopyprint?
  1. template <class T>  
  2. void Xor_Swap_3(T& a,T& b)  
  3. {  
  4.          int size = sizeof(T);  
  5.          for (int i =0;i<size;i++)  
  6.          {  
  7.                    *((unsignedchar*)(&a)+i) = (*((unsigned char*)(&a)+i)) ^ (*((unsignedchar*)(&b)+i));  
  8.                   *((unsigned char*)(&b)+i) =(*((unsigned char*)(&a)+i)) ^ (*((unsigned char*)(&b)+i));  
  9.                    *((unsignedchar*)(&a)+i) = (*((unsigned char*)(&a)+i)) ^ (*((unsignedchar*)(&b)+i));  
  10.          }  
  11. }  
template <class T>void Xor_Swap_3(T& a,T& b){         int size = sizeof(T);         for (int i =0;i<size;i++)         {                   *((unsignedchar*)(&a)+i) = (*((unsigned char*)(&a)+i)) ^ (*((unsignedchar*)(&b)+i));                  *((unsigned char*)(&b)+i) =(*((unsigned char*)(&a)+i)) ^ (*((unsigned char*)(&b)+i));                   *((unsignedchar*)(&a)+i) = (*((unsigned char*)(&a)+i)) ^ (*((unsignedchar*)(&b)+i));         }}

    这个版本的函数不仅能交换两个整数、任何指针、float数和double数,更牛逼的是它能交换两个用户定义类型的变量!事实上它基本上是在内存一级上操作数据,而任何类型的数据对象最终都表现为内存对象。这其实就是通过内存拷贝实现两个对象的交换的一个版本吧,当然还有利用memcpy等手段进行内存拷贝来实现两个变量的交换的,这里我就不赘述了。

结束语

    本篇到此写完了,有种不好的感觉,因为文中大量使用“强制类型转换”而这个东西是C++中容易出错的地方,而我再写本文时,并没有去复习关于强制类型转换的相关知识,因此担心很多地方有潜在的出错可能,还请各位砖家指正!

 

本文出自:http://blog.csdn.net/w57w57w57/article/details/6673611

原创粉丝点击