指针指针
发布日期:2022-02-15 02:36:23 浏览次数:12 分类:技术文章

本文共 5454 字,大约阅读时间需要 18 分钟。

指针指针

C 语言是最常使用指针的语言之一,我们在初学 C 语言时可能就会因为指针这个概念而头疼,我在这里将重述指针在 C 语言里的作用及使用过程。

尽管其它高级语言中可能并没有明显地使用指针的痕迹,但实际上指针仍然蕴含在那些高级语言的细微之处,可以说涉及到对地址的引用操作离不开指针的概念。

指针的定义

在计算机科学中,指针(英语:Pointer),是编程语言中的一类数据类型及其对象或变量,用来表示或存储一个存储器地址,这个地址的值直接指向(points to)存在该地址的对象的值。 ——维基百科

在1964年,哈罗德·劳森发明了最早的指针。他在PL/I中实现出了这个概念,其他高级编程语言也很快跟进,使用了这个想法。

如果想要理解指针的概念,我们就不能不去观察它的使用过程。

首先我们需要回忆下我们学习 C 语言指针时的痛苦记忆,我在此提出以下几个一堆问题:

当我们在 C 语言中使用指针时,我们究竟在使用什么?操作指针意味着什么?为什么为指针变量进行赋值时,有时被赋值的变量前需要加&?加&与不加&的区别在哪里?为什么使用scanf()为变量进行输入时,需要在变量前加&,而printf()则不需要呢?

借着解答这些问题的过程我们来深刻理解指针的操作。

scanf() 函数对变量输入需要 &

当我们需要对变量使用scanf进行输入时,例:

#include
int main(){
int a; scanf("%d", &a); printf("%d", a); return 0;}

变量前需要添加&,这个符号意味着取地址,意味着把a这个变量的地址传入scanf()函数内,之所以需要传入地址,而不是一个普通变量是因为这个scanf()函数它是个函数。

你以为我说了句废话?其实不是的。

让我们回忆下我们刚学到函数时,我们的老师是否有向我们提过,函数的参数传递是值传递或者地址传递,即使没有学过或者忘记了并不要紧,我将再次重述函数的 C 语言函数参数传递知识。

C语言函数参数传递

当我们创建一个自定义的函数,然后为这个函数传递一个变量进去,并且期望自定义函数能够改变这个变量的值时,我们最终会发现这个期望极其容易落空。

#include
void changValue(int a){
a = 2;}int main(){
int a = 1; changValue(a); printf("%d", a); return 0;}

a的输出结果实际上还是 1 ,changeValue并不能真的改变a的值。

因为这里新建的changeValue函数对a执行的是值传递,意思是只将a的值传递给函数中新声明的a,这两个实际上并不相等,如果这个世界上存在着和你长相完全一样的人,那他也不会成为你,因为你们有着完全不同的生活经历。

但是我们可以通过传递地址的方式,从源头上改变a的值。

#include 
void changeValue(int *a){
*a = 2;}int main(){
int a; a = 1; changeValue(&a); printf("%d", a); return 0;}

这里的输出结果就是 2 了,因为我们是将a的地址,也即&a作为参数传递进了changeValue函数,函数中声明了一个指针变量a,这个指针指向的就是传进函数内a的地址。

如果我有台时光机,穿越回过去,把一个和你婴儿时期完全一样的克隆人婴儿和婴儿时期的你进行交换,他就能成为你。

我们应该要明晰一点,我们存储在电脑上的数据是存放在电脑的硬盘或者其它存储媒介中,不同的文件格式有不同的编码方式,但它们最终是按照电脑的编码方式才能存放在电脑的存储媒介中的。如果电脑不获取到文件存放在存储媒介上的地址,我们能改变文件内容吗?自然不能。

C 语言内变量的地址也是如此,我们传进函数参数如果是普通的变量,那么函数只会把它的值复制一份给函数体中声明的形参,但是如果传递的是地址,即使是地址的副本,函数内的形参也会依据这个地址找到存放的数据,改变地址所指向的值也就是在改变存放着的值,那么原先参数所表示的值也会改变。

游戏中,我使用的角色和你使用的角色无论怎么打,都不会影响到现实中的,但如果你顺着网线从电脑那头爬到了我这头,我是一定会报警的,这里就体现了函数的值传递和地址传递。

这一点我们需要注意函数如果想要改变一个变量,并且这个改变能够体现到函数外,就需要变量的地址,而不是变量的值

scanf()的作用在于为变量输入一个值,实际上是需要改变变量的,那么scanf()就需要变量的地址。

printf()的作用只是打印变量的值,它没有改变变量,也就不需要传递变量的地址。

这里我们顺便提一嘴C语言的函数作用域。

C语言函数作用域

C语言的函数作用域使得函数内的声明的变量的生命只会存在该函数内,离开了该函数即被销毁,这个销毁是从存储空间上的销毁,所以如果期待一个有着全局作用域的指针变量去保存函数内的声明的变量地址是不理想的。例:

#include
void changeA(int *a, int b){
b = 3; a = &b;}void changeA1(int *a, int *b){
int c = 3; *b = c; *a = *b;}int main(){
int *a; int b = 0; a = &b; changeA(a, b); printf("%d, %d\n", *a, b); changeA1(a, &b); printf("%d, %d", *a, b); return 0;}

它的输出结果如下所示:

我们来分析下changeA函数的作用。

void changeA(int *a, int b)我在声明函数changA时同时又声明了形参int *a, int b,我在上节中提到值传递与地址传递,这里的形参获取到的是实参*a的地址,但是在函数内,我却将指针a又重新指向了b的地址,这个b所依赖的是值传递,它只是保存了实参b的值,而非地址,实际上它是在这个函数中创建的。

changA()这个函数执行完会发生什么?形参b会被销毁,b的地址也就不存在了,那么形参指针a指向哪里呢——一个存放空数据的地址,在C语言内空数据可以等于 0,这也就解释了为什么输出结果为什么是0。

changA1()则不同了,它里面的形参ab实际保存的是实参ab的地址。地址b的解引用*b也只是保存了局部变量c的值而已,并未重新指向其它地址,同时形参a也获取了*b的值,同时指针a仍然指向b,即使函数中未写*a = *b,输出结果仍然是3, 3

指针的使用

指针从它的名字的含义可以看出它需要指向某个事物,这个事物可以看成是变量的地址。如果一个指针偏偏不愿指向某个变量的地址,它就会成为“野指针”,也就是一个指向不明的指针。

指针变量的写法貌似有点奇怪,我们声明指针变量时使用int *a之类的语句,可是如果把变量b赋值给它时,使用的是a = &b,输出a的值时,使用的却是printf("%d", *a),仅仅是赋值输出就有不同讲究了,初学时显然容易感到困惑。

#include
int main(){
int *a; int b = 3; a = &b; printf("%d\n", b); scanf("%d", a); printf("%d", *a); return 0;}

输出结果:

我们首先来分析int *a,这是一个非常标准的声明语句,int表示类型,*是一个标识,表明b这个变量是一个指针变量,实际上我觉得写成int * a这样反而更明确一点,这样就直接表示aint型的指针或者int 指针型,不过C语言的类型并没有指针型这一说法。

赋值语句a = &b的含义是将b的地址赋值给a,因为a是指针,指针需要的是地址。

那么输出时需要使用printf("%d", *a);而不是printf("%d", a),则是因为我们想要看到是a指向的地址上存放的值,而不是a指向的地址本身,尽管地址可以输出,但是每次存放数据的位置是会变化的,输出地址在这里并不重要。printf("%d",*a)*a表示对a这个地址的解引用,也就是获取a地址上存放的数据之含义。

实际上这个时候,a地址等于的是&b,而b的值才等于*a

#include 
int main(){
int *a; int b = 3; a = &b; printf("%d\n", a); printf("%d", &b); return 0;}

下图是如上的输出,表明&b == a

image-20201112183940639

正因为a变量在赋值后实际是一个地址值,所以在scanf()语句中,它并没有使用&

特殊的指针——数组变量名

为什么我胆敢宣称数组变量名是一个特殊的指针呢,因为数组变量名与指针具有相似的地方——它们均指向一个地址。

当我们为一个数组进行输入时,是不需要使用取址符的,因为数组变量名指向数组存储的首个单元的地址。

#include
int main(){
int a[3] = {
1, 2, 3}; printf("%d\n", a); printf("%d", &a[0]); return 0;}

下图是如上的输出,输出了a&a[0],它们的值相同,说明变量名a相当于数组单元首个数据所存放位置的地址。

指针变量的输入也是不需要取址符的,因为指针变量相当于它所指向的变量的地址,而上述的例子正说明了数组变量名也是地址,因为它们指向的地址相等。

实际上,数组变量名是在编译时被转换为指向数组首个单元地址的指针,如果失去这个转换的过程,自然数组变量名也就不是指针了,但是我们可以选择忽略这个转换的过程,而直接说数组变量名等同于指向数组首个单元地址的指针。

指针与结构体变量

结构体在声明时会划分一块地址,有点像数组,但是数组变量名是首单元地址,而结构体变量名却是结构体变量起始地址所在的数据。

#include 
typedef struct{
int data; int data2;} Stu;int main(){
Stu a; a.data = 2; a.data2 = 3; printf("%d", a); return 0;}

看起来结构体变量名类似于“反数组变量名”

另外如果使用结构体指针,需要某结构体变量内的某个值,可以有两种写法:

#include 
typedef struct{
int data; int data2;} Stu;int main(){
Stu a; Stu *b; a.data = 2; a.data2 = 3; b = &a; printf("%d\n", a); printf("%d\n", b->data); printf("%d\n", (*b).data); return 0;}

b->data(*b).data是这两种写法的体现,实际上后者依然是先对指针变量b来个解引用再取值的过程,只不过需要注意运算符的优先级,将*写在括号内,提升它的优先级。前者写法则减少了敲键盘次数,实乃懒癌患者福音。

typedef 提供的可能性

在声明结构类型我们可以使用类似如下语句:

#include
typedef struct {
int data;} * Stu;int main(){
Stu b; return 0;}

需要注意的是这个时候声明变量b时,变量b实际上是一个指针变量,这是typedef提供的功能,那么我们就可以用b指向一个具有相同类型的结构体变量的地址了。

#include
typedef struct {
int data;} * Stu, Stu1;int main(){
Stu b; Stu1 a; a.data = 2; b = &a; printf("%d", b->data); return 0;}

小节

指针变量再如何特殊,它也只是一个指向地址的变量,差异的发生是因为它所处的环境不同导致不同的作用,但它们作为指针的本质却是相同的。

u=2828975428,2694375333&fm=26&gp=0

随心所写,有所误人子弟之处烦请指正,小子当垂泪涕零以感激。

相关参考 :

C语言中文网·《结构体与指针》http://c.biancheng.net/cpp/html/94.html

C语言中文网·《C语言和内存》http://c.biancheng.net/cpp/u/c20/

CSDN· hasakei_《scanf为什么要取地址,而不直接使用变量名》 https://blog.csdn.net/weixin_39846515/article/details/79177776

转载地址:https://blog.csdn.net/PeZXO/article/details/109671670 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:CSS预处理器——sass教程(3)
下一篇:如何利用js来控制音频的播放次数

发表评论

最新留言

路过,博主的博客真漂亮。。
[***.116.15.85]2024年04月24日 09时40分30秒