由于主调函数的变量a,b与被调函数的形参x,y它们相互独立。函数 swap 可以修改变量x,y,但是却无法影响到主调函数中的a,b。
现在利用取地址运算符,分别打印它们的首地址,让我们从内存的角度,来分析一下它们。
a在内存中为首地址10484860开始的 sizeof(int) 字节。
b在内存中为首地址10484856开始的 sizeof(int) 字节。
x在内存中为首地址10484832开始的 sizeof(int) 字节。
y在内存中为首地址10484836开始的 sizeof(int) 字节。
调用 swap 函数时,a的值1,传给x。b的值2,传给y。
图中,红色数值为数据对象首地址,黑框内的为变量名和值。
即使x,y已经交换了,但是并未影响a,b。
由于在被调函数内部无法直接修改主调函数的变量。那么我们采用迂回战术,在函数 main 中取得a、b 的指针。将两个指针传递到函数 swap 。那么,在函数 swap 内部可以根据这两个信息修改a、b。
这下,我们就需要用到指针类型作为参数了。
现在将 x 、 y 改为了 int * 类型的指针。在主调函数中,对 a , b 进行取地址获取指针并传入函
数 swap 。在函数 swap 内部,通过这两个指针交换目标数据对象的值。注意,不是交换指针x,y的值, 而是交换目标数据对象a,b的值。所以,需要在指针前使用取值运算符*。
图中,红色数值为数据对象首地址,黑框内的为变量名和值。
现在终于能解释为何在使用 scanf 函数时,需要对变量先取地址再传入参数了。
int n; scanf("%d", &n);
scanf 会从读取从键盘的输入,转换后存储到变量n当中。被调函数 scanf 无法直接修改在主调函数中的变量n。因此,我们将变量n的指针传入 scanf 函数。通过指针使得被调函数间接地修改主调函数中的变量。
再次强调,指针内保存的不仅仅是目标数据对象首地址,指针的类型也非常重要。要在内存中找到一个数据对象,需要有以下两个信息。
- 数据对象的首地址。
- 数据对象占用存储空间大小。
指针的值保存着数据对象首地址,指针类型对应着目标数据对象的类型,用于标记目标数据对象的空间大小和指针运算时的步长。
char * ,目标数据对象大小为 sizeof(char) 。运算时,步长为sizoef(char)。
short * ,目标数据对象大小为 sizeof(short) 。运算时,步长为sizoef(short)。
int * ,目标数据对象大小为 sizeof(int) 。运算时,步长为sizoef(int)。
long * ,目标数据对象大小为 sizeof(long) 。运算时,步长为sizoef(long)。
long long * ,目标数据对象大小为 sizeof(long long) 。运算时,步长为sizoef(long long)。
float * ,目标数据对象大小为 sizeof(float) 。运算时,步长为sizoef(float)。
double * ,目标数据对象大小为 sizeof(double) 。运算时,步长为sizoef(double)。
若要用函数 swap 交换两个int类型的变量,必须传入指向这两个int类型变量的指针。函数内部可以通过指针知道对象的首地址和类型。但是,这样也使得函数 swap ,只能交换int类型的变量了。
如果,想让函数 swap 函数更加通用一点,可以交换更多类型的变量。应该怎么做呢?
由于指针类型定死了指针所指向的数据类型。为了让函数可以交换更多的数据类型,我们仅需要指针类型中保存的首地址,目标数据大小通过额外的参数传入。
void swap(void *x, void *y, int size)
int * 修改为 void * 。类型为 void * 的指针仅保存首地址,不保存目标数据对象的空间大小。所以, 不能对 void * 类型的指针进行取值。同样的,它也没有步长,所以不能对 void * 类型的指针进行加减运算。
int n;
void *p = &n; // int *赋值给void *,类型信息被丢弃,仅保存首地址。
*p; // 仅有首地址,未保存目标数据对象大小,无法取值。
p + 1; // 仅有首地址,没有步长,无法进行加减运算。
但是, void * 有一个好处,那就是任意类型的指针都可以直接赋值给它。而其他类型的指针是不能相互赋值的,由于赋值会改变目标数据对象的类型。
char *pc; int *pn;
pc = pn; // 编译出错,目标数据对象类型不同,无法直接赋值。
void *p;
p = pn; // 编译通过,任意类型的指针都可以直接赋值给它。
p = pc; // 编译通过,任意类型的指针都可以直接赋值给它。
规律:
我们将函数定义修改为:
void swap(void *x, void *y, int size)
{
// 指针转为char *,单个字节操作内存
char *pX = (char *)x; char *pY = (char *)y; char temp;
for (int i = 0; i < size; i++)
{
temp = pX[i]; pX[i] = pY[i]; pY[i] = temp;
}
}
由于 void * 不能取值和加减,所以我们将其转换为 char * 。 char * 可以提供单个单个操作内存的能力。
在C语言中 void * 类型不但可以接受任意类型的指针,也可以自动转换为任意类型的指针。
但在C++中,规则稍微严格了一点, void * 仅能接受任意类型的指针,不能自动转换为其他类型的指针。为了保证代码的兼容性,我们将 void * 强制转为 char * ,避免在C++中编译出错。
char *pX = (char *)x; char *pY = (char *)y;