变量值互换自定义函数swap()的使用

最近在学习排序的过程中,发现很多排序的程序中都会用到数组元素值互换的一小段代码。然后想着直接把元素值的互换写成函数以后直接调用即可,当时命名为`void change(int * a, int * b)。`
昨天在学习别人的排序程序时看见了swap()函数,这时候我才进一步知道了这个不是标准库函数却胜似标准库函数的函数,上网搜索`swap()`之后,又学到了不少新东西。学习就是一个积累的过程,在查漏补缺的过程中慢慢学习,写博客则是温故而知新的不错选择,可能在写的过程中又会出现不解的问题,正好可以及时学习,所以今天总结一下自己对`swap()`的学习。
在最初接触C的时候,我们都知道一个简单的整型值互换(变量`a=10,b=20`值互换)的程序:
int t = 0;
t = a;
a = b;
b = t;

这段代码虽然简单,但是在当时学习的过程中不禁惊呼:哇塞,好神奇。现在想想其实这段代码虽然简单但是说实在的,其中也是包含了变量定义、初始化、赋值以及简单的内存的知识,正所谓麻雀虽小五脏俱全。
swap()常见的有以下四种形式:

void swap_1 (int * , int *);//经典型
void swap_2 (int * , int *);//取巧型
void swap_3 (int * , int *);//诡异型
void swap_4 (void * a, void *b, size_t size);//泛型

1、void swap_1 (int * , int *);//经典型

swap()函数的写法最简单的一种写法便是我们初次接触的这段代码。


void swap_1 (int * a, int * b)//a,b变量接收了要比较的两个变量的地址
{
    int temp;//temp作为互换时的中间介质,起到了一个避免数据丢失的作用
    temp = * a;
    * a = * b;
    * b = temp;
}

比较时只需要调用swap_1(&a,&b);发送地址即可。再看这段代码的时候,只是角度不同了(有了函数和指针的思想),其他也没有什么了,这里我也就不啰嗦说那么多了。以下三种是新学到的,虽然都只是用来互换变量内容的,但是思想上有了更多的趣味性。

2、void swap_2 (int * , int *);//取巧型

void swap_2 (int * a, int *b)
{
    *a = *a + *b;
    *b = *a - *b;
    *a = *a - *b;
}

我们会发现,swap_2()中并没有采用像swap_1()temp似的任何中间变量,只是*a,*b之间经过了加加减减的几步操作之后便实现了变量值的互换。这些是完全的数学运算的奥妙。但是我们不妨分析一下,也好在这枯燥的代码中找到点乐子(←_←)。

首先假设*a =A;*b =B;
那么第一行:*a=*a +*b=A+B,此时*a的值就已经变了,变成了最初的*a*b的和
之后第二行:*b=*a -*b=A+B-*b=A+B-B=A,*b的值变成了原始的*a的值A
最后第三行:*a=*a - *b=A+B-*b=A+B-A=B,*a的值则变成了最初的*b的值B
互换完成。

这种东西一般人是自己不会发现的,因为这是完全的数学的魅力。而我们要做的就是学会去欣赏这种细微的奥妙,取巧的魅力。

3、void swap_3 (int * , int *);//诡异型

void swap_3 (int * a, int *b)
{
    *a = *a ^ *b;
    *b = *a ^ *b;
    *a = *a ^ *b;
}
//补充(time2016112111:18:35):
//需要注意的是当:a == b即,两个变量的地址相同时,该方法有bug:
//* a ^ *b == *a ^ *a == *b ^ *b == 0
//因为a,b相同,则经一次运算:*a == *b == 0
//后面无论是多少次异或结果都是:*a ^ *b = 0

为什么说是诡异型呢,因为我们发现这段代码赋值等号右端始终只有一种运算,那就是*a ^ *b,而就是这一种运算便解决了互换的问题,真是玄之又玄。
首先^是异或运算符,在逻辑运算中法则为:如果a、b两个值不相同,则异或结果为1。如果a、b两个值相同,异或结果为0。

0^0 = 0; 
1^1 = 0;
0^1 = 1^0 = 1;

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

a ^b ^ c = a ^ (b ^ c) = (a ^ b) ^ c;
d = a ^ b ^ c 可以推出 a = d ^b ^ c;
a ^ b ^ a = b.等

那么,让我们来分析一下:
假设:*a = A;*b =B;
第一行:*a = *a ^ *b = A^B;
第二行:*b = *a ^ *b = A^B^B = A^0 = A;
第三行:*a = *a ^ *b = A^B^A = A^A^B = 0^B = B;
经过一系列的逻辑运算,最终实现了互换的效果。这点便体现出了逻辑运算的奥妙与魅力。也是非常的有趣而且有点绕,一眼看不出来,自己也很难想出这种代码,那就继续欣赏(受虐→_→)吧。

4、void swap_4 (void * a, void *b, size_t size);//泛型

swap_4 ()是对于swap_1()的拓展,可以互换非整型变量,但是比较的两个变量必须是同种类型。我自己现在也有些是不能够完全理解的,因为牵扯泛型的问题以及一些对于自己来说还不常用及不常见的问题。而且在网上很难找到对该代码的详细解释。代码如下:

void swap_2 (void * a, void *b, size_t size)
{
    unsigned char * p = (unsigned char *)a;//强制类型转换
    unsigned char * q = (unsigned char *)b;//强制类型转换
    unsigned char medium;

    while(size--)
    {
        medium = *p;
        *p = *q;
        *q = medium;
        p++;
        q++;
    }
}

调用时为:

swap_4(&a,&b,sizeof(char));//char类型互换
swap_4(&a,&b,sizeof(int));//int型互换
swap_4(&a,&b,sizeof(double));//double型互换

首先形参部分(void * a, void *b, size_t size),以前没有接触过,经过查找资料之后,才有所了解:其中void是”无类型”,则void * 为”无类型指针”,void * 可以指向任何类型的数据,所以可以发送char、int、float、double等类型的地址。

对于void*的使用可以参考void与void*详解

size_t是一个变量类型,存储的便是sizeof()返回的值。对于该代码的实现我将结合下图来说明(画图前我不明白while循环到底是怎么实现不同类型可以实现调换的,画完图后我突然自己明白了:

调换之前:

这里写图片描述

调换之后:
这里写图片描述

为什么要对a,b强制类型转换呢?为什么转换为unsigned char *类型呢?
因为不知道要互换的a,b是什么类型,所以要以最小内存开始互换,如图中以int型为例,假设存储的数据分别为:

1111 1111 1111 1111 1111 1111 1111 1111

0000 0000 0000 0000 0000 0000 0000 0000

a和b现在内存只有第一个字节,p和q分别存储了a和b的地址,对第一个字节的内容先进行调换。由于size=sizeof(int)=4(32位机),while循环会执行四次,每执行一次size--,size=0时退出循环,p++q++使得此操作对int的四个字节的内容分别进行了互换。最终实现整体的互换效果。

对于charwhile循环只实现一次,而double循环执行8次即可实现整体互换。

  • 10
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值