C语言--从指针到二级指针(下)

通过上一篇博文,我们已经对指针有了一定的认识,在接下来的这一篇博文中,我们将进一步了解指针的精髓,包括指针的实质以及一些特殊的指针,同时还会涉及到一些C语言其他的语言特性。

一:指针的实质

指针作为C语言的精髓关键在于“指”,那这个“指”到底是什么?C99中曾给出了指针的明确定义,归纳起来有十分重要的两点:1. 指针是一种引用类型 2.指针本身的类型取决于它所引用的数据类型。怎么理解呢?如果想要完全理解这两点,那就说来话长了,甚至涉及到一些计算机的硬件结构,比如堆内存和栈内存,这有违本文主旨,其次这些我也不熟,姑且就不在此大谈特谈误人子弟了。长话短说,在计算机中,每一个内存单元都是编址的,所以每一个内存单元理所应当的有一个独一无二的地址编号,这个地址不过是个无符号整数,当计算机对内存进行写入或读取时就需要靠这个整数来寻址。在C语言中,我们对这个用整数进行编号的地址进行了适当抽象,并赋予了一个名字,即为指针,这也就不难理解上一篇博文中为什么我们打印出来的指针为什么是一个无符号整数了,所有指针本身的值构成了一个集合,这个集合就是在当前系统进行寻址时的寻址范围。简单说来就是:指针本身是一个变量,只不过这个变量存放的内容是计算机中的数据的地址,我们通常把指针理解为“指向”其他变量的一种特殊数据类型。前段时间看到有关指针引发的争论:指针究竟是地址还是引用?双方各执一词,似乎都有道理,其实这两者并不矛盾。


回到C99中的那两点,如果我们向一个内存单元中存入了一个int类型的数据,那么这个数据对应的指针就是一个int*类型的引用类型。同理,如果存入一个double类型的数据,那指针就是一个double*类型的的引用类型。比如有一个int*类型的指针a,里面放了一个int型整数5。那我们对指针进行间接寻址运算,*a == 5输出为true,所以我们说指针变量a引用了int型变量5。

此外,指针变量还支持加减运算,这和我们常见的加减有所不同,简单说来对指针加一实质上是获取了下一个存储单元的地址,具体可以参考下面关于数组与指针的讨论。

下面我们来看看指针与数组的关系,一旦我们在程序中声明了一个数组,编译系统就会在内存中分配一块固定大小的存储空间,这个空间是一块连续的存储区域,同时数组名含有一个特殊的意义,它是存放数元素的连续存储空间的首地址,即为指向了数组中第一个元素的常量指针。看一个简单例子:

#include <stdio.h>
#include <stdlib.h>
int main(void) {
	int a[] = { 1,2,3,4 };
	for (int i = 0; i < 4; i++) {
		printf(" the address of a[%d] is: %p \n", i, a + i);
		printf(" the value of a[%d] is: %4d \n" ,i, *(a + i));
	}
	system("pause");
	return 0;
}

运行结果如下:


很明显,在这个程序中数组名a居然被我们拿来像指针一样使用了,同时我们发现,指针变量a每加1,a就指向了数组中的下一个元素,细心观察还可以发现指针加1,后,指针的值增大4,这是因为在计算机中每8位就是一个存储单元,即每8为对应一个地址编号,而一个int型整数为32位,对应4个存储单元,指针加1指向下一个数组元素的过程中,指针“移动了”4个存储单元(学名叫做指针偏移)。

如果我们把指针同二维数组联系起来则将大大增加可玩性,但不过是一些花哨的奇技淫巧,比如下面4个其实是同一个东西:a[i][i], *(a[i] + j),*(*(a + i) + j), (*(a + i))[j], 感兴趣的同学可以自行谷歌之,通常不建议将这种令人费解的代码写入程序中,这是花式作死的成功典范。

二 特殊的指针

指针本身讲来讲去也就那些内容,而指针“指向”的内容则让它变得高深莫测。常见的不过是“指向”一些常见数据类型的数据或者变量,再特殊一点“指向”一个数组,再高深一点就是一些“指向”另一个指针的指针,即二级指针,结构体指针,指针函数,函数指针之类的,对于这些,本文并不会一一俱到。接下来我们主要讨论结构体指针和二级指针。

学习过C语言的同学对结构体都不会陌生,因为C语言自带的数据类型显然满足不了我们的所有需求,这个时候我们就需要自定义数据类型了,也就是我们所说的结构体。关于结构体相信大家一定有所了解,我们就不展开来谈了,下面我们来看看它和指针会碰撞出什么样的火花?看看下面这种情况会发生什么?我们定义了一个结构体,这个结构体中的一个成员变量是一个指针,同时这个指针“指向”了这个结构体本身,不多说,直接看代码:

typedef struct stu {
	int ID;		//the ID of  student
	struct stu* nextStrudent;
}Student;		//define a student

这段代码无非是定义了一个代表学生结构体,比较特殊的是将结构体分成了两个数据域,第一个数据域存放了学生的ID,第二个数据域存放了一个“指向”另一个学生的指针,这就实现了一种单向链接的关系,每一个学生相当于一个节点,效果就是他们实现了一种排队的效果,特殊之处在于每一个人只知道他后面是谁,但对他前面的那个人却一无所知(这就是我们在数据结构中所说的单向链表,如果这样讲下去,那就真的没完没了了,所以我们点到为止)。如图所示:

最后值得一提的就是二级指针了,即“指向”指针的指针,如果你看完这两篇博文已经对“指向”的实质有了深刻的认识,那么二级指针自然也就难不倒你。

在上一篇博文中我们已经看到了C语言函数值传递的本质,通过代码的运行结果可以看出:如果不使用return或者指针,C语言函数调用结束后并不能改变调用函数时传入的实参。同理,当调用函数时我们传入的实参就是一个指针变量,如果我们希望在调用函数结束后该指针能够“指向”其他数据或者变量,也得使用return或者该指针的指针(二级指针),我们在这里只介绍二级指针,代码为证:

#include <stdio.h>
#include <stdlib.h>

void changeValue(int** pt, int b) {
	**pt = b;
}

int main(void) {
	int a = 3;
	int* p = &a;
	printf("  before change value, a = %d \n", a);
	changeValue(&p, 5);
	printf("  after change value, a = %d \n", a);
	system("pause");
	return 0;
}
运行结果如下:


虽然这段代码写得很糟糕,只是强行拿来说事,我们还是来看图吧:


可以看到最终pt指向了5,而pt是由&p赋值得来的,故&p最终也指向了5,而&p又是由a经过两次取地址而得到的二级指针,故a的值最终为5。从中我们可以看到二级指针的奇妙之处。

以上就是有关指针的核心内容,感兴趣的同学可以参考更多的文献加以了解更多关于指针的知识!最后附上上一篇博文的链接:C语言---从指针到二重指针(上)



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值