课程地址
前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函数里到底发生了什么,
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步:这是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指针含义的深刻解析