目录
-
指针与数组步长问题探究
指针与数组步长问题探究
为何探究
不管是对于初学者还是对于码农而言,指针与数组都是一个难点,而研究指针与数组的步长问题有助于我们从根本上去理解和解决一些实际的问题。
探究一维数组与一级指针
#include <stdio.h>
#include <stdlib.h>
void fun(char *str) {
printf("str:%d\tstr+1:%d\n",a,a+1); //str:6422282 str+1:6422283 步长:1字节
}
int main(void) {
char *p;
int *q;
char a[10] = "abcd";
printf("p:%d\tp+1:%d\n",p,p+1); //p:3837952 p+1:3837953 步长:1字节
printf("q:%d\tq+1:%d\n",q,q+1); //q:0 q+1:4 步长:4字节
printf("s:%d\ts+1:%d\n",s,s+1); //s:11609152 s+1:11609153 步长:1字节
printf("a:%d\tp+1:%d\n",a,a+1); //a:6422282 p+1:6422283 步长:1字节
printf("&a:%d\t&a+1:%d\n",&a,&a+1); //&a:6422282 &a+1:6422292 步长:10字节
fun(a);
}
对于以上代码结果的解释如下:
笔者这里总结了一下指针的解释规则:指针的步长在定义指针的时候就已经确定了,指针的步长等于其指向的内存的大小。所以:
- p指针定义时就说了指向char类型的内存,即p的步长为1字节。
- q指针定义时就说了指向int类型的内存,即q的步长为4字节。
- s指针定义时就说了指向char类型的内存,(这里尽管malloc了空间,但是要好好理解笔者所说的"定义时就已经确定了步长"),即s的步长为1字节。
- str指针形参定义时就说了指向char类型的内存,即str的步长为1字节。
笔者这里也总结了一下一维数组的解释:一维数组名即为数组首元素地址,实质上也是一个指针。&一维数组名即为数组的首地址,这与数组首元素的地址值并不等价。所以:
- 一维数组名a实质是一个指针,指向数组的首元素地址,而数组的首元素地址为char类型的内存,所以a的步长为1字节。所以说这也就是为什么可以把a传入fun函数中当实参,因为他们的步长都一样,所以a[i]和*(str+i)一样。
- &a,即数组的首地址,步长即为数组的内存大小,所以&a的步长为1*10=10字节
值得注意的是:
int main() {
char *p;
char a[10] = "abcde";
p = a; //合法,因为p和a的步长一样
p = &a; //不合法,但是编译器会将其处理为p = a;的样子,所以会有一个警告说p要等于&a的话应该定义为:char (*p)[10];的样子,这个会在下面解释,耐住性子看下去吧qwq
}
探究二维数组与二级指针
#include <stdio.h>
#include <stdlib.h>
void fun1(char **q) {
printf("q:%d\tq+1:%d\n",q,q+1); //q:6422280 q+1:6422284 步长:4字节
}
void fun2(char (*s)[5]) {
printf("s:%d\ts+1:%d\n",s,s+1); //s:6422280 s+1:6422285 步长:5字节
}
int main(void) {
char **p;
char b[][5] = {"abcd","bcde","cdef","efgh"};
printf("p:%d\tp+1:%d\n",p,p+1); //p:4194432 p+1:4194436 步长:4字节
printf("b:%d\ta+1:%d\n",b,b+1); //b:6422280 b+1:6422285 步长:5字节
printf("&b:%d\t&b+1:%d\n",&b,&b+1); //&b:6422280 &b+1:6422300 步长:20字节
fun1(b);//不合法,有警告
fun2(b);//合法
}
对于以上代码结果的解释如下:
二级指针也是指针,所以也符合上面笔者所总结的规则:指针的步长在定义指针的时候就已经确定了,指针的步长等于其指向的内存的大小。所以:
- 二级指针的定义就是指向指针的指针,而指针的内存块大小为4个字节,所以二级指针p的步长为4个字节。
- 二级指针形参q,也是定义的时候就说明了是指向指针的指针,所以q的步长也为4字节。
笔者这里先总结以下二维数组的解释规则:二维数组可以看作是多个一维数组组成的,二维数组名即为二维数组的首行元素地址(可以理解为整个一维数组的首地址&一维数组名),这与二维数组首元素地址并不完全等价,(尽管值一样,但是步长不一样,一个步长是一整行,一个步长是一个元素)所以:
- 这里的b为二维数组的首行元素地址,其实就是相当于整个一维数组的首地址,相当于&a(char a[5]),所以b的步长为1*5=5字节。
- &b为整个二维数组的首地址,所以步长为1*4*5=20字节。
- s的步长解释见下面。
PS:不管是一维数组,还是二维数组,其本质上都是一个指针。为了方便理解,可以将其看成一个特殊的指针,比如&二维数组名,可以看出是一个指向整个二维数组的指针,所以&二维数组名的步长为其二维数组的内存大小。
补充:上面的代码中函数fun1()的步长和传入的实参的步长不一致,前者为4字节,后者为5字节,所以会导致在函数fun1中打印等问题中会有错误,不合法,程序可能会崩溃。一般情况下,二维数组通常与数组指针连用,也就是函数fun2()的样子,采用数组指针的目的就是为了设定指针的步长与传入的二维数组的步长一致。所以说函数fun2()中s的步长大小为char * 5 = 1 * 5 = 5字节。
值得注意的是:
int main() {
char (*p)[5];
char b[][5] = {"abcd","bcde","cdef","efgh"};
p = b; //合法,因为p和b的步长一样
p = &b; //不合法,但是编译器会将其处理为p = b;的样子,所以会有一个警告说p要等于&b的话应该定义为:char (*p)[4][5];的样子,其实这个时候的p就跟三维数组c[][4][5]中的三维数组名c一样qwq
}