C语言学习——彻底搞定C指针

一、指针是什么

这是一个生活中的例子:比如说你要我借给你一本书,我到了你宿舍,但是你人不在宿舍,于是我把书放在你的 2 层 3 号的书架上,并写了一张纸条放在你的桌上。纸条上写着:你要的书在第 2 层 3 号的书架上。当你回来时,看到这张纸条,你就知道了我借与你的书放在哪了。你想想看,这张纸条的作用,纸条本身不是书,它上面也没有放着书。那么你又如何知道书的位置呢?因为纸条上写着书的位置嘛!其实这张纸条就是一个指针了。它上面的内容不是书本身,而是
书的地址,你通过纸条这个指针找到了我借给你的这本书。
那么我们 C/C++中的指针又是什么呢?请继续跟我来吧,下面看一条声明一个指向整型变量的指针的语句:

int *pi;

pi 是一个指针,其实,它也只不过是一个变量而已,如下图。

(说明:这里我假设了指针只占 2 个字节宽度,实际上在 32 位系统中,指针的宽度是 4 个字节宽的,即 32 位。)
由图中可以看出,我们使用“int *pi”声明指针变量 —— 其实是在内存的某处声明一个一定宽度的内存空间,并把它命名为 pi。能在图中看出pi 与前面的 i、 a 变量有什么本质区别吗?没有! pi 也只不过是一个变量而已。那么它又为什么会被称为“指针”?关键是我们要让这个变量所存储的内容是什么。现在我要让 pi 成为具有真正“指针”意义的变量。请接着看下面语句

pi = &i;

&i 是什么意思呢?这是返回 i 变量的地址编号!整句的意思就是把 i 地址的编号赋值给 pi,也就是在 pi 里面写上 i 的地址编号。结果如下图所示:

执行完 pi=&i 后,在图示中的内存中, pi 的值是 6。这个 6 就是i 变量的地址编号,这样 pi 就指向了变量 i 了。pi 与那张纸条有什么区别? pi 就是那张纸条!上面写着 i 的地址,而 i 就是那个本书。现在看懂了吗?因此,我们就把 pi 称为指针。所以要记住,指针变量所存的内容就是内存的地址编号!好了,现在我们就可以通过这个指针 pi 来访问到 i 这个变量了,看下面语句:

printf("%d", *pi);

那么*pi 什么意思呢?你只要这样读它: pi 的内容所指的地址的内容,就是 pi 这张“纸条” 上所写的位置上的那本 “书”—— i 。你看, Pi 的内容是 6,也就是说 pi 指向内存编号为 6 的地址。 *pi嘛,就是它所指地址的内容,即地址编号 6 上的内容了,当然就是 30 这个“值”了。所以这条语句会在屏幕上显示 30。也就是说printf("%d", *pi)等价于printf("%d", i) ,请结合上图好好体会!到此为止,你掌握了类似&i、 *pi 写法的含义和相关操作吗?总的一句话,我们的纸条就是我们的指针,同样我们的 pi 也就是我们的纸条!剩下的就是我们如何应用这张纸条了。这有一个程序:

char a,*pa;
a = 10;
pa = &a;
*pa = 20;
printf("%d", a); 

能看出输出的结果是什么吗?答案是输出20,好了,就说到这了。

二、函数参数的传递

我们都知道: C 语言中函数参数的传递有: 值传递、地址传递、引用传递
这三种形式,接下来就有三道题目,来试试吧。

1. 函数参数传递方式一:值传递

程序代码如下:

void Exchg1(int x, int y)
{
    
    
int tmp;
tmp = x;
x = y;
y = tmp;
printf("x = %d, y = %d\n", x, y);
}
main()
{
    
    
int a = 4,b = 6;
Exchg1(a, b);
printf("a = %d, b = %d\n", a, b);
return(0);
} 

输出的结果为:

x = ____, y=____.
a = ____, b=____. 

问下划线的部分应是什么?

输出结果是:

x = 6, y = 4.
a = 4, b = 6. 

为什么不是 a = 6,b = 4 呢? 做这道题需要理解值传递的形式,让我们一起看一下调用 Exch1 函数的代码:

main()
{
    
    
int a = 4,b = 6;
Exchg1(a, b) /* 这里调用了 Exchg1 函数 */
printf("a = %d, b = %d.\n", a, b);
} 

Exchg1(a, b)时所完成的操作代码如下所示。

int x = a; /* ← */
int y = b; /* ← 注意这里,头两行是调用函数时的隐含操作 */
int tmp;
tmp = x;
x = y;
y = tmp; 

请注意在调用执行 Exchg1 函数的操作中人为地加上了头两句:
int x = a;
int y = b;
这是调用函数时的两个隐含动作。它确实存在,现在只不过把它显式地写了出来而已。问题一下就清晰起来啦。 (看到这里,现在你认为函数里面交换操作的是 a、 b 变量或者只是 x、 y 变量呢?)
原来 ,其实函数在调用时是隐含地把实参 a、 b 的值分别赋值给了 x、 y,之后在你写的 Exchg1 函数体内再也没有对 a、 b 进行任何的操作了。交换的只是 x、 y 变量。并不是 a、 b。当然 a、 b 的值没有改变啦!函数只是把 a、 b 的值通过赋值传递给了 x、 y,函数里头操作的只是 x、 y 的值并不是 a、 b 的值。这就是所谓的参数的值传递了。
正是因为它隐含了那两个的赋值操作,才让我们产生了前述的迷惑(以为 a、 b 已经代替了 x、 y,对 x、 y 的操作就是对 a、 b 的操作了,这是一个错误的观点!)。

2. 函数参数传递方式二:地址传递

继续!地址传递的问题!
看第二题的代码:

void Exchg2(int *px, int *py)
{
    
    
int tmp = *px;
*px = *py;
*py = tmp;
printf("*px = %d, *py = %d.\n", *px, *py);
}
main()
{
    
    
int a = 4;
int b = 6;
Exchg2(&a, &b);
printf("a = %d, b = %d.\n”, a, b);
return(0);
} 

问输出的结果为多少?

*px=____, *py=____.
a=____, b=____. 

最终程序的输出结果为

*px = 6, *py = 4.
a = 6, b = 4. 

看函数的接口部分: Exchg2(int *px, int *py),请注意:参数 px、py 都是指针。
再看调用处: Exchg2(&a, &b);
它将 a 的地址(&a)代入到 px, b 的地址(&b)代入到 py。同上面的值传递一样,函数调用时作了两个隐含的操作:将&a, &b 的值赋值给了 px、 py。
px = &a;
py = &b;
我们发现,其实它与值传递并没有什么不同,只不过这里是将 a、 b的地址值传递给了 px、 py,而不是传递的 a、 b 的内容,而整个 Exchg2 函数调用是如下执行的:

px = &a; /* ← */
py = &b; /* ← 请注意这两行,它是调用 Exchg2 的隐含动作。 */
int tmp = *px;
*px = *py;
*py = tmp;
printf("*px =%d, *py = %d.\n", *px, *py); 

这样,有了头两行的隐含赋值操作。 我们现在已经可以看出,指针 px、 py
的值已经分别是 a、 b 变量的地址值了。接下来,对*px、 *py 的操作当然也就是对 a、 b 变量本身的操作了。
所以函数里头的交换就是对 a、 b 值的交换了,这就是所谓的地址传递(传递 a、 b 的地址给了 px、 py) 。

3. 函数参数传递方式三:引用传递

问题3的代码如下:

void Exchg3(int &x, int &y) /* 注意定义处的形式参数的格式与
值传递不同 */
{
    
    
int tmp = x;
x = y;
y = tmp;
printf("x = %d, y = %d.\n", x, y);
}
main()
{
    
    
int a = 4;
int b = 6;
Exchg3(a, b); /*注意:这里调用方式与值传递一样*/
printf("a = %d, b = %d.\n”, a, b);
} 

最后输出结果为:

x = 6, y = 4.
a = 6, b = 4. /*这个输出结果与值传递不同。 */ 

与值传递相比,代码格式上只有一处是不同的,即在定义处:
Exchg3(int &x, int &y)
但是我们发现 a 与 b 的值发生了对调。这说明了 Exchg3(a, b)里头修改的是 a、 b 变量,而不只是修改 x、 y 了。
我们先看 Exchg3 函数的定义处 Exchg3(int &x, int &y)。参数 x、y 是 int 的变量,调用时我们可以像值传递(如: Exchg1(a, b); )一样调用函数(如: Exchg3(a, b);)。但是 x、 y 前都有一个取地址符号“&”。有了这个,调用 Exchg3 时函数会将 a、 b 分别代替了 x、 y 了,我们称: x、 y分别引用了 a、 b 变量。 这样函数里头操作的其实就是实参 a、 b 本身了,也就是说函数里是可以直接修改到 a、 b 的值了。

三、总结

最后对值传递与引用传递作一个比较:

1)在函数定义格式上有不同:
值传递在定义处是: Exchg1(int x, int y);
引用传递在这义处是: Exchg3(int &x, int &y);

2)调用时有相同的格式:
值传递: Exchg1(a, b);
引用传递: Exchg3(a, b);

3)功能上是不同的:
值传递的函数里操作的不是 a、 b 变量本身,只是将 a、 b 值赋给了 x、 y。
函数里操作的只是 x、 y 变量而不是 a、 b,显示 a、 b 的值不会被 Exchg1 函数所修改。

引用传递 Exchg3(a, b)函数里是用 a、 b 分别代替了 x、 y。函数里操作
的就是 a、 b 变量的本身,因此 a、 b 的值可在函数里被修改的。

参考文献: 彻底搞定C指针(著 姚云飞 修订 丁正宇 )

猜你喜欢

转载自blog.csdn.net/Insincerity/article/details/108825129
今日推荐