探究实参与形参它们相互独立
由于主调函数的变量
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 *
由于指针类型定死了指针所指向的数据类型。为了让函数可以交换更多的数据类型,我们仅需要指针类型中保存的首地址,目标数据大小通过额外的参数传入。
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 *
类型为特例,它可以接受任意指针类型的赋值,也可以赋值给任意类型的指针。
我们将函数定义修改为:
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;