漫谈C语言指针和数组

指针一直是被很多人误解,包括很多书中对指针的定义也存在很大的歧义,导致看书的人越看越懵,所以指针到底是个什么东西?(注意:文中所有图片不表示实际内存大小,纯属为了画图需要)

指针是地址吗?

很显然指针不是地址,地址只是一个数值,这个数值不可能代表指针。如图

伪代码:

int *ap = (int *)100;

long *bp=(long *)100;

*bp=1101;

指针变量bp 和指针变量ap存的地址值都是100,但是同样的100地址值代表的是不同的空间 一个是long 类型 1101,一个是Int类型 11 。

所以结果已经很明了,脱离了类型的地址不能称为指针,指针是一种数据类型,它的数据类型表明了它是指向某一类型的对象,而它的值是地址。指针一种广泛的数据类型统称,就像整型包含了short、int、long类型。

int a=1;

int *p=&a;

如上  a代表了一个int类型的数据对象,它在内存中有一个地址,&a 是一个指针,该指针是int* 类型    值是a在内存中的地址,&a  是指向a的指针。我们把这个&a指针,赋值给int *类型的变量p。

所以p是一个指针变量,但是为了方便称呼,很多时候我们就说p指针指向了a。

这里有些人会认为&a是取a的地址,这不完全是对的,一个地址是无法给一个int*  类型变量赋值,这会报错或者警告,只有同类型的才能赋值给该类型的变量。指针赋值指针变量才是正确的。

如果写  int *p=0xff30 肯定会被编译器警告,一个值类型int不能赋值给int* 指针类型,但是也能运行,那是因为0xff30 会被隐适转换位int* 类型 ,值是0xff30 。

指针只能指向自己相对应类型的数据对象,譬如定义一个int* 的指针,那只能指向int*类型 ,不能指向其他类型的数据对象,否则编译器会警告,并且被隐适转换成int* 类型

 int a=1;

short b=1;

int *ap = &a;         //正确,&a是int *类型指针  赋值给int* 类型变量ap

short *bp  =&b;    //正确,&b是short *类型指针  赋值给short* 类型变量bp

int *op = &b;        //错误,&b是short *类型指针  赋值给int* 类型变量op

当然上面运行也没问题,指针&b中的地址保持不变,类型被隐适转换成变成int* 类型,赋值给变量指针变量op

这里op虽然拿到的是b的地址,但是指向的东西不一样,*bp 指向的一个short类型,*op指向的是一个Int类型,不过b后面那个空间的数据是未知的,所以图中直接用空白表示

指针面对数组也是如此,但很多人面对数组就直接搞不清楚了,下面讲到数组会再次讨论这么问题

 

指针可以参与加减运算,结果是产生同类型却指向另一个内存空间对象的指针,如:

int  a=1;

int *p =&a;

int *op = p+1;

这边p+1会产生了一个新的指针,这个新指针中的地址,跨越当前int对应的字节数,例如原来a的地址是10,int表示4个字节,那么+1后新的地址是14,+2后新的地址是18

如果p是short *类型的指针,那么+1,新的地址就是7,+2新的地址是9。说白了就是对指针+1 ,直接跨过当前指针指向的那个数据。如图

 

数组是一块连续分配的存储同一数据类型对象的空间,数组有一维数组和多维数组,多维可以看成一维数组,只不过每个元素又是一个数组而已

C语言中指针和数组是不同的东西,只是在表达式中数组大多数会隐适转换成首元素的指针。

指针是指向某一个类型空间的  数据类型对象(值是空间的地址)

  int arr[3]={1,2,3}   这就定义了一个数组,并且初始化了数据,通过数组名加下标的方式就可以访问数组中的每一个元素。

arr[0] 表示第一个数组元素,arr[1]表示第二个数组元素,为什么说数组就是采用了指针的方式呢?往下看

 

前面我们说指针变量必须赋值一个对应类型的指针才不会报错,那么我们试试这样写

int *p=arr; 完全没有任何警告,所以发现了没 数组名arr是一个指向int类型的指针,它指向的是数组的第一个元素。我们把arr赋值给int类型变量试试看会发生什么?

编译器警告说 int和int*间接级别不同,arr是被解释为int *类型。

分别输出数组的第一个,和第二个元素(1,2),如果arr是指向数组的指针,那当我们计算arr+1  也直接是跨越整个数组了,而不可能还输出2

其实arr[0],arr[1]  实际也是转换成 *(a) 和  *(a+1) 来计算的,因为这里arr是转换为指向第一个元素的指针,所以可以通过+1,+2便利整个数组。 当然arr作为左值时是一个不可修改的左值,也就是不能改变的,所以a=a+1这样操作是不行的,很多人说数组名是常指针所以不能赋值这是错误的讲法。数组名就是数组的标识符而已,跟普通类型的变量没什么区别。

所以看到没其实指针还可以通过下标的方式来访问,这不是数组的访问方式哦,不要以为是因为定义了数组,所以才可以用arr[0]来访问。

准确的说,数组在大多数情况下是会隐适转变成指向首元素的指针来用,当参与sizeof 或者 &运算符等除外

譬如上面那个例子   int* p  =arr ; 是正确的,因为arr数组被隐适转换成了首元素的指针

譬如sizeof(arr)  我们发现是输出数组的长度 12,  

我们看下这个:  sizeof(*(&arr))  输出的也是数组的长度12,   arr是数组,所以&arr是数组的指针,所以   *(&arr)  也是数组,所以结果是12

但是我们Int  *p=  *(&arr),完全没问题,因为上面说过了  数组大多数情况会被隐适转换成首元素指针来用,所以在这个表达式中*(&arr) 又被当成了首元素的地址了(数组表达式转换为首元素指针)   

因为栈的存储方式是从高地址到低地址的,所以我们取指针的时候要从最后面开始,这里我用的是c-free工具mingw 编译调试,不是用VS,因为VS下定义的局部变量,内存空间的排序不是连续的,也就是abc之间会空出很多,导致无法定位。

那&a是什么? 答案很肯定:&a是指向数组的指针。看下就知道了

从警告中我们看出,&arr被解析成一个int(*)[3] 的指针类型,说明它是一个指向数组的指针,而之前看过过arr是被解析成 int *类型。

我们可以这样定义一个指向arr数组的指针变量arrp,   int (* arrp)[3]=&arr;  

这个时候我们对arrp执行加减操作,执行跨越的是整个数组,假如这里int是4个字节arrp+1 实际地址是+12,因为数组有3个Int类型元素

我们可以看到+1和不加的地址相差了12,而且+1后的指针,指向的区域是另外一个数组,里面的每个数组元素都是随机的int类型值。

 

下面我们看下二维数组 int arrs[2][3]={{1,2,3},{4,5,6}}

我们只要想像成一维就可以了,{1,2,3}是arrs数组的第一个元素,{4,5,6}是arrs数组的第二个元素,那么很简单了

数组arrs转换为指向{1,2,3}这个子数组的指针,arrs+1 就是指向第二个数组{4,5,6}的指针,&arrs是指向整个大数组的指针,两个都是数组指针

只不过如果通过arrs+1,那么实际地址+12,而通过&arrs+1 ,那么实际地址加的是24,(假设int都是4字节)

&arrs因为是整个数组的指针,所以即使+1后,也是一个二维数组的指针

我们看一个例子:a,b,c分别是多少?

int arrs[2][3]={{1,2,3},{4,5,6}};

int (*p)[3]=arrs;

int a = (int)p;

int b = (int)*p;

int c = (int)**p;

我们分析下:

第二句申明了一个数组指针变量p ,arrs是也一个数组指针变量, 它指向的是{1,2,3},而p也是指向一个有3个Int类型的数组指针,所以arrs可以直接赋值给p,不会产生任何的警告,所以其实p和arrs一样都是数组{1,2,3}的指针

第二句,p指针中的地址就是{1,2,3}的地址,也等于元素1的地址,指针p通过强制转换为Int类型,a保存的是元素1的地址(毕竟参与转换的话肯定是变量的值吧,也就是那个地址去转换成Int)

第三句, p是指向数组{1,2,3}的指针,那*p 就是数组,然后*p  数组被隐适转换成的是首元素指针用 (很好记,你只要知道除了sizeof  和  & 外其他都是隐适转换成首元素指针就好了)

首元素的指针,强制转为Int 也是把存储的地址转为为了int 。

第四句 p是指向数组{1,2,3}的指针  那么*p就是数组{1,2,3} ,而数组作为右值表达式时会转换为指向首元素的指针,  也就是指向1的指针,那么**p就是1,其实这边不用强制转换,题目是为了迷惑故意加上去,不然你肯定猜到**p也是一个int类型了

 

如果把p后面的[3]改成[2]呢,答案其实一样,只不过这个时候p指向的是数组{1,2}了,在arrs在转换成p的时候地址还是原来的地址。

 很多人误解指针,就是过多考虑指针右边赋值是什么,指针只要记住:我定义的是什么类型的指针,我指向的就是什么类型的数据,至于你是什么,我不管,不知道,也不关心!

 

 

 

 

 

 
 
 
posted @ 2022-09-11 10:27  自由小菜园  阅读(70)  评论(0编辑  收藏  举报