赞
踩
最近在学习排序的过程中,发现很多排序的程序中都会用到数组元素值互换的一小段代码。然后想着直接把元素值的互换写成函数以后直接调用即可,当时命名为`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);//泛型
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);发送地址即可。再看这段代码的时候,只是角度不同了(有了函数和指针的思想),其他也没有什么了,这里我也就不啰嗦说那么多了。以下三种是新学到的,虽然都只是用来互换变量内容的,但是思想上有了更多的趣味性。
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
互换完成。
这种东西一般人是自己不会发现的,因为这是完全的数学的魅力。而我们要做的就是学会去欣赏这种细微的奥妙,取巧的魅力。
void swap_3 (int * , int *);//诡异型
void swap_3 (int * a, int *b)
{
*a = *a ^ *b;
*b = *a ^ *b;
*a = *a ^ *b;
}
//补充(time:2016年11月21日11: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;
经过一系列的逻辑运算,最终实现了互换的效果。这点便体现出了逻辑运算的奥妙与魅力。也是非常的有趣而且有点绕,一眼看不出来,自己也很难想出这种代码,那就继续欣赏(受虐→_→)吧。
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
的四个字节的内容分别进行了互换。最终实现整体的互换效果。
对于char
型while
循环只实现一次,而double
循环执行8次即可实现整体互换。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。