lecture1-4:通用swap函数

课程地址
    前4个lecture主要就讲了指针,不同类型之间转换时的内存是如何变化的。下面将通过描述通用swap函数的执行过程记录下我学习课程的收获。
    顾名思义,swap函数是将传入的两个对象的值进行交换,在c++中,有现成的使用模板实现的swap函数可以直接调用,当然自己实现一个也不难,使用引用可以很方便地实现对象之间内容的交换,但是在c中,就只能使用指针实现。话不多说,直接开始吧。

版本1.0
void swap(int *pa,int *pb)
{
    int temp = *pa;
    *pa = *pb;
    *pb = temp;
}

接下来我们来看看调用函数时到底发生了什么,首先我们应该明白的是传入函数的参数是什么,两个指针,指针是什么?指针本质上可以理解为存地址的变量(你心里可能会产生疑问:既然指针都是存地址,那为什么指针还有类型之分,难道地址还有不同的类型?,后面会解释这个问题),它和int,double类型的变量本质上其实没有什么区别,都是变量,都用来存储信息,但是,为什么指针能让人望而生畏?慢慢看下去你就会发现它的独特之处。
下面我们通过图来理解下swap 1.0函数里到底发生了什么,

swap1.0.png

    a,b表示两个想要被交换的int类型的数,pa,pb表示两个指针变量,这里为什么使用箭头将pa,pb指向a,b的起始地址,因为pa,pb的值,其实就是a,b变量在内存空间的起始地址。在函数中int temp = *pa,我们定义了一个变量temp,将pa指针指向的值取出(可以理解为先将pa变量中存储的地址取出,再用该地址去取值,即得到图中的a的值),这个过程也叫解引用,存入temp中,然后同样将pb指向的值取出,放到pa指向的值中,最后将变量temp中存的值存入pb指向的值中,也就是下图中的1,2,3步:
swap1.0.png

    这是1.0版的swap,理解了指针的含义函数的执行过程还是不难理解。我们发现,这个函数它只能用来交换int类型的值,如果我想交换两个float类型的数,怎么办,没办法只能再写一个一样的函数,将函数中出现的int全都变为float,但是可以看出这种解决方式并不优雅,会使得代码重复累赘,所以我们换一种方式来解决这个问题。

swap2.0
void swap(void *vp1,void *vp2,int size)
{
    int buffer[size];
    memcpy(buffer,vp1,size);
    memcpy(vp1,vp2,size);
    memcpy(vp2,buffer,size);
}

    为了解决我们swap1.0中的存在的问题,我们需要一个能交换任意类型数据的函数,所以这次我们不能将传入的参数的类型限制为特定的类型,而是使用void*类型,即可以传入任意类型的指针,最后一个参数是一个int类型的size,这个有什么用?后面会解释。回想一下,之前swap1.0的实现思路是声明一个变量,用于临时存储值,从而交换两个指针指向的值,在swap2.0中,我们将传入的参数改为了void*,难道不能像swap1.0那样直接定义个临时变量实现值的交换吗?答案是不能,为什么?原因主要有以下两点:
1.不能声明void类型的变量
2.在swap1.0中,我们传入的参数是int*类型的指针pa,当我们解引用时,编译器知道这个指针是int*型(即知道它里面存储的是一个int类型变量的地址),再一次强调指针类型的变量存的就是地址,所以解引用时它将pa中存的地址取出,作为起始地址,从该地址开始接着后面取4个字节(int类型的值使用4个字节存储),然后将其解释为一个int类型的数值,这样我们就得到的pa解引用的值。但是在swap2.0中,指针没有了类型,我们在取出地址后,无法知道应该取出该地址后的几个字节,所以不能使用swap1.0的思路实现swap2.0。
    上面的原因2也回答了之前的问题,为什么都是存地址,指针却有类型之分,原因就在于有类型的指针在解引用时指针的类型能让编译器知道应当取出几个字节,知道应当取出几个字节后我们就能使用memcpy函数实现内存间交换。
    目前为止我们的swap函数通用性是有了,但是调用时也应当时刻注意,要交换的到底是什么数据,当你将它用于交换字符串变量时,可能会出现错误(可以先自己尝试写一下交换字符串变量的函数调用再看下面的解释)。

char * husband = "husband";
char * wife = "wife";
swap2(husband,wife,sizeof(char*));

可能一开始会写出这样的代码,并且输出结果可能就是你想要的,然后你觉得就是这样的,但是这段代码从逻辑上就是错的,下面我们看看是为什么可能输出对的结果,以及为什么它是错的。
一开始可能觉得字符串指针存的就是字符串首地址,我们想交换字符串的话直接传入字符串的首地址,传入字符串长度,swap2就能实现数据交换,但是这样的话交换的内容是什么,是字符串,这里有一个问题是直接交换字符串所在的内存的话,我们如何确定交换的数据块的大小,如果两个字符串长度不一样如何处理?所以这样是行不通的,但是为什么这样可能得到正确的结果,因为你传入的sizeof(char*)的值为8(在64位操作系统中),而初始化的两个字符串长度恰好又没有超过8,所以可能输出正确的结果(更大的看可能是程序崩掉)。正确的交换方式应当是这样: swap2(&husband,&wife,sizeof(char*));,想一下为什么,这样调用的话我们交换的是什么,一开始husband,wife两个变量分别存储的是两个字符串的首地址的值,而我们传入husband,wife的地址,即交换两个变量中存的值,也就是字符串的首地址,交换完成后,husband变量中存的就是“wife”的首地址,这样我们交换字符串的目的也就达到了。
完整代码如下:

#include <stdio.h>
#include <assert.h>
#include <string.h>
int swap1(int *pa,int *pb)
{
    int temp = *pa;
    *pa = *pb;
    *pb = temp;
}

void swap2(void *vp1,void *vp2,int size)
{
    int buffer[size];
    memcpy(buffer,vp1,size);
    memcpy(vp1,vp2,size);
    memcpy(vp2,buffer,size);
}


int main(int argc, char const *argv[])
{
    int a = 23,b = 44;

    //swap1 test
    printf("a : %d  b : %d\n", a , b);
    swap1(&a,&b);
    printf("after swap  a : %d  b : %d\n\n", a , b);
    swap1(&a,&b);

    //swap2 test for int
    printf("a : %d  b : %d\n", a , b);
    swap2(&a,&b,sizeof(int));
    printf("after swap  a : %d  b : %d\n\n", a , b);
    swap2(&a,&b,sizeof(int));

    //swap2 test for double
    double d1 = 32.1,d2 = 44.1;
    printf("d1 : %.2lf  d2 : %.2lf\n", d1 , d2);
    swap2(&d1,&d2,sizeof(double));
    printf("after swap  d1 : %.2lf  d2 : %.2lf\n\n", d1 , d2);
    swap2(&d1,&d2,sizeof(double));

    //swap2 test for string
    char * husband = (char*)"husband";
    char * wife = (char*)"wife";

    printf("husband : %10s    wife : %10s\n", husband,wife);
    swap2(&husband,&wife,sizeof(char*));
    printf("husband : %10s    wife : %10s\n\n", husband,wife);

    return 0;
}

参考博客:
void及void指针含义的深刻解析

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354

推荐阅读更多精彩内容

  • 指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构; ...
    朱森阅读 3,440评论 3 44
  • iOS面试小贴士 ———————————————回答好下面的足够了------------------------...
    不言不爱阅读 1,978评论 0 7
  • 1.写一个NSString类的实现 +(id)initWithCString:(c*****t char *)nu...
    韩七夏阅读 3,765评论 2 37
  • importUIKit classViewController:UITabBarController{ enumD...
    明哥_Young阅读 3,805评论 1 10
  • 1.C和C++的区别?C++的特性?面向对象编程的好处? 答:c++在c的基础上增添类,C是一个结构化语言,它的重...
    杰伦哎呦哎呦阅读 9,515评论 0 45