1.      初始题目

面试题:不用额外的变量,实现一个Swap函数,交换两个参数的值(问题)

这个题目太经典,也太简单,有很多人都会不假思索结出答案:

  1. //Code 1
  2. void Swap(int* a, int* b)
  3. {
  4. *a = *a + *b;
  5. *b = *a - *b;
  6. *a = *a - *b;
  7. }

但真正的难点来了,接下来,就会有面试的第二问:指出code1算法的问题,并修正(问题)

2.      错误思路

一些毁人不倦的书,把其中的问题归结到了溢出(overflow)。修改方法是用异或替代加减,即:

  1. //Code 2
  2. void Swap(int* a, int* b)
  3. {
  4. *a = *a ^ *b;
  5. *b = *a ^ *b;
  6. *a = *a ^ *b;
  7. }

诚然,在很多场合,大数的加法都会带来溢出的问题。但Code1 中的Swap函数真会构成溢出问题么?(问题)

2.1 错误的想法

大家都能清楚的描述加法溢出的产生原因:运算结果超出了变量的表示范围。

发生溢出时,结果会是多少呢?有很多人是知道的,但可能表述得不太清晰:舍弃超出变量内存范围的高位(加法只会舍弃最高位),保留在范围内的低位。

用这个概念,去分析问题3,恐怕是很难得到明确的答案的。很多人简单看看,认为执行两个很大的数的加法后,运算结果丢了最高位,所以会构成问题。

和问题3的知识点相同的,有另外一个问题:

判断两个int型变量相加,是否会溢出?不会溢出返回,否则返回非整数。下面的方法是否可行?(问题)

  1. //Code 3
  2. int IsOverFlow(int x, int y)
  3. {
  4. int sum = x + y;
  5. return (sum - x == y) && (sum - y == x);
  6. }

对问题3,回答“会构成问题”的同学。回答问题4时,应该会认为“方法可行”。理由是,认为溢出后,信息丢失,以后的运算都不再能得到正确结果。

2.2  加法的溢出

事实上,问题3的答案是:不会溢出;问题4的答案是:方法不可行。

用一下数学符号表述,可以清晰的得到正确答案。

假设一个整型变量有w位,那么,整型变量的表示范围是: -2w-1 ~ 2w-1-1。 当求和结果超出这个范围时,就发生溢出。即正数的绝对值大于2w-1-1时,发生正溢出,负数绝对值大于2w-1时,发生负溢出

加法的溢出,只会发生在两个求和参数同正负符号的时候。

第w-1位

(符号位)

第w-2位

第w-3位

…………

第2位

第1位

第0位

一正一负时,肯定不会发生溢出,因为这时:|x +y| < max{|x|, |y|}

对于两个很大的正数相加,发生正溢出时,第w-1位肯定为1,假设除去最高位时,剩下w-1位(第0至w-2位)的内存可以表示为数:r。即 x + y = r + 2w-1

由于是有符号数,第w-1位是符号位,由于符号位是1,说明结果是负数,采用补码,即运算结果为:sum =- (2w-1 – r) = r-2w-1。即于sum = r + 2w-1-2w=x + y - 2w

同时,两个绝对值很大的负数相加,发生负溢出后的结果是: s = x + y + 2w

当两个正数产生加法溢出时,运算结果s = x + y – 2w。 此时, s < 0。

再做减法:s – x = x + y - 2w - x = y + 2w 由于|y| <2w-1,有 |y - 2w| >2w-1 ,发生负溢出,在结果上又加上了 2w。即,x + y - 2w – x + 2w ,这个结果等于y。

因此问题3的答案是“不构成问题”,问题4的答案是:不可行。

判断两个int型变量相加,是否会溢出?不会溢出返回,否则返回非整数。这个函数需要怎么设计?(问题)

发生正溢出后,结果将是负数;发生负溢出后,结果将是正数。

因此可以这样做:

  1. //Code 4
  2. int IsOverFlow(int x, int y)
  3. {
  4. int sum = x + y;
  5. int posOver = (x > 0) && (y > 0) && (x + y < 0);
  6. int negOver = (x < 0) && (y < 0) && (x + y > 0);
  7. return posOver || negOver;
  8. }

3.      解决问题

回顾一个经典的算法,快排:

  1. //Code 5
  2. void QSort(int a[], int start, int end)
  3. {
  4. if (start >= end)
  5. return;
  6. int lastLessIdx = start;
  7. for (int traveler = start + 1; traveler <= end; ++traveler)
  8. {
  9. if (a[traveler] < a[start])
  10. {
  11. Swap(&a[++lastLessIdx], &a[traveler]);
  12. }
  13. }
  14. Swap(&a[lastLessIdx], &a[start]);
  15. QSort(a, start, lastLessIdx - 1);
  16. QSort(a, lastLessIdx + 1, end);
  17. }

其中用到了Swap函数。但是,直接用code1中给出的Swap函数行吗?

不行。Code1中的Swap函数,没有考虑自己与自己换的情况。当需要自己与自己交换时(快排中lastLessIdx + 1 == traveler 时),Code1直接把值变成了0。

因此,修正方法很简单,判断是不是自己在和自己交换即可:

  1. //Code 6
  2. void Swap(int* a, int* b)
  3. {
  4. if (a == b)
  5. return;
  6. *a = *a + *b;
  7. *b = *a - *b;
  8. *a = *a - *b;
  9. }

从Swap函数谈加法溢出问题的更多相关文章

  1. swap()函数的几种写法及优劣

    试用几种方法实现swap函数,比较效率高低. 首先说结果,最快的是赋值交换. 原因分析 gcc开启O2优化后,三个函数的汇编代码一样.是的,除了第一行的文件名,一模一样. 附代码 void swap1 ...

  2. 关于swap函数传值的问题

    #include <stdio.h> void swap(int * p3,int * p4); int main() {  int a = 9;  int b = 8;  int * p ...

  3. 自己写一个swap函数交换任意两个相同类型元素的值 对空指针的使用 字节大小的判断(二)了解原理

    验证的代码: #include <stdio.h> int main(){ char c = 'z'; ) + (c << ) + () + 'a'; printf(" ...

  4. swap函数的四种写法

    swap 函数的四种写法 (1)经典型 --- 嫁衣法 void swap(int *a, int *b) { int temp; temp = *a; *a = *b; *b = temp; } ( ...

  5. c++ swap 函数

    转载地址 1,最通用的模板交换函数模式:创建临时对象,调用对象的赋值操作符. template <class T> void swap ( T& a, T& b ) { T ...

  6. [转]谈谈C++中的swap函数

    1,最通用的模板交换函数模式:创建临时对象,调用对象的赋值操作符. template <class T> void swap ( T& a, T& b ) { T c(a) ...

  7. [Effective C++ --025]考虑写出一个不抛异常的swap函数

    引言 在我的上一篇博客中,讲述了swap函数. 原本swap只是STL的一部分,而后成为异常安全性编程的脊柱,以及用来处理自我赋值可能性. 一.swap函数 标准库的swap函数如下: namespa ...

  8. [020]转--C++ swap函数

    原文来自:http://www.cnblogs.com/xloogson/p/3360847.html 1.C++最通用的模板交换函数模式:创建临时对象,调用对象的赋值操作符 template < ...

  9. Linux Kernel ‘exitcode_proc_write()’函数本地缓冲区溢出漏洞

    漏洞名称: Linux Kernel ‘exitcode_proc_write()’函数本地缓冲区溢出漏洞 CNNVD编号: CNNVD-201311-061 发布时间: 2013-11-07 更新时 ...

随机推荐

  1. 在C#里实现各种窗口切换特效,多达13种特效

    原文:http://www.cnblogs.com/clayui/archive/2011/06/28/2092126.html 预览:   下载 这次clayui给大家带来了比较实用的东西,因为时间 ...

  2. CF_Lucky Sum

    幸运数字的定义是这样:仅含4和7且不比n小的数为n的幸运数字. 输入范围l,r要求输出这个范围内的数字的幸运数字之和. 代码: #include<stdio.h> #define N 10 ...

  3. NAND Flash内部结构简介

    介绍     1965年,在双极管被W.Shockley.W.Brattain和J.Bardeen三人发明出来之后,Intel的合作创始人Gordon Moore发现了这样一条法则:当价格不变时,集成 ...

  4. Linux中修改环境变量及生效方法

    在/etc/profile文件中添加变量[对所有用户生效(永久的)] 用VI在文件/etc/profile文件中增加变量,该变量将会对Linux下所有用户有效,并且是“永久的”. 要让刚才的修改马上生 ...

  5. BZOJ2295: 【POJ Challenge】我爱你啊

    2295: [POJ Challenge]我爱你啊 Time Limit: 1 Sec  Memory Limit: 128 MBSubmit: 126  Solved: 90[Submit][Sta ...

  6. devpress GridControl控件绑定RepositoryItemImageComboBox 作为下拉框使用 zt

    1.拖出gridview控件,然后将字段绑定上去 2.将要做下拉框的控件加入RepositoryItemImageComboBox控件 3.绑定数据 ; i < ; i++) { //如果取值时 ...

  7. 大型邮箱smtp服务器及端口 收集

    各大型邮箱smtp服务器及端口收集: 新浪邮箱smtp服务器 外发服务器:smtp.vip.sina.com 收件服务器:pop3.vip.sina.com 新浪免费邮件 外发服务器:smtp.sin ...

  8. 设置Div多行文本超出时,以省略号代替

    这个文章不错 http://www.css88.com/archives/5206 css中有一个属性: text-overflow,可以设置文本超出指定长度后的文本截取样式. 下面是从 w3shco ...

  9. python 零散记录(六) callable 函数参数 作用域 递归

    callable()函数: 检查对象是否可调用,所谓可调用是指那些具有doc string的东西是可以调用的. 函数的参数变化,可变与不可变对象: 首先,数字 字符串 元组是不可变的,只能替换. 对以 ...

  10. Apache支持.htaccess配置方法

    打开httpd.conf文件用文本编辑器打开后,查找  代码如下 复制代码 Options FollowSymLinks AllowOverride None 改为: Options FollowSy ...