C++ 语法之数组指针
一维数组:
如果我们定义了一个一维数组,那么这个数组名,就是指向第一个数组元素的地址,也即,是整个数组分配的内存空间的首地址。
比如 int a[3]; 定义了一个包含三个元素的数组。因为一个int占4个字节,那么系统就会为这个数组分配12个字节的内存空间,用来存储这个数组。
可以运行下面代码来观察每个元素的地址:
int main() {int a[3];a[0] = 1;a[1] = 20;a[2] = 33;cout << "a的地址:" << a << "\n";for (int i = 0; i < 3; i++)cout << &a[i] << "\n";}
运行结果:
可以看到内存的首地址是FAA8开始,注意这里的a和a[0]的地址是相等,因为它们都是代表数组的第一个地址。
然后依次加4个字节,FAAC,FAB0。
那么因此得出结论,数组名元素值每加1,它就会访问到下个四字节的地址(注意这里是根据类型的)。
那么a[3]就应该是FAB4,注意这里数组定义三个,下标是从0开始的,所以到2就结束了。
但我们依然可以通过a[3]访问到FAB4,只不过不是合法的,你不能写入,因为这不是属于你的内存。
如下例子:
#include <iostream>using namespace std;int main() {int a[3];a[0] = 1;a[1] = 20;a[2] = 33;cout << "a的地址:" << a << "\n";for (int i = 0; i < 3; i++)cout << &a[i] << "\n";cout << "a[3]的地址:" << &a[3] << "-------a[3]的值:" << a[3]; //访问a[3]的值,int类型4个字节}
结果: (注意访问a[3]是不合法的,只为示例使用)
可以看到a[3]的地址,是在a[2]的基础上加了个四个字节,这就是数组的规则。其实可以看作是指针的一种运用。
所以一维数组,我们可以像定义int 变量指针来定义数组指针,如下:
#include <iostream>using namespace std;int main() {int a[3];a[0] = 1;a[1] = 20;a[2] = 33;int* p = a;cout << *p << "\n" << p[1] << "\n" << *(p + 2);}
输出值:
这里要特别说是*(p+2)这个访问,因为指针变量不像正常变量,比如int a;
a+1的话,它就会在a的值加1, 指针变量+1的意思,是在原来的地址上,增加该类型的字节数。这个是根据指针类型来决定的。比如int类型指针加1就是,4字节的+4,+2就是8。
可以通过输出地址值来查看,比如:
int* p = a;int* p1 = p + 1;cout << p << "\n" << p1 << endl;
二维数组:
接下来说一下二维数组
比如定义了一个int b[2][3],可以看成是什么,可以看成是我们定义了2个一维数组a[3]。我们分开来看。
那么b[0]就是一个内存地址,指向了第一个a[3]一维数组的地址。
而b[1]就是指向了第二个a[3]一维数组的首地址。
可以通过地址观察验证:
#include <iostream>using namespace std;int main() {int b[2][3];b[0][0] = 1;b[0][1] = 20;b[0][2] = 33;//输出二堆数组的每个地址:for (int i = 0; i < 2; i++)for (int j = 0; j < 3; j++)cout << "b[" << i << "][" << j << "]地址:" << &b[i][j]<< endl;//输出b[0]和b[1]地址:cout << "\n\nb[0]地址:" << b[0] << "\nb[1]地址:" << b[1] << endl;
}
结果:
可以看到定义了一个二维数组,根据地址来看,在内存中是跟一维数组同样的分配方法,依次增加,每次四个字节,F5E8,F5EC,F5F0.....
只不过是以二维的层次来解读的,通过两个下标来控制,比如b[2][3]数组,你找到规则后,你就可以像一维数组那样推断出b[4][2]是哪个地址,访问的数据段是哪里(当然同上,这种访问也是不合法的,只为示例说明)
分解出来后:
那么b[0]即b[0][0]的的首地址,这个跟前面一维数组同样的原理,a跟a[0]地址一样。
而b[1]的地址,就是要加上12个字节,即F5F4,即b[1][0]首地址。
这个12个字节,就是包含3个整数的一维数组的跨度,这不难理解,跟前面都对应上了。
所以现在问题来了:
即现在二维数组b应该是什么样的指针指向它?它加1是多少?
(推理过程,如果不适请跳过直接看结果)
现在是二维数组b包含了两个一维数组a[3];
可以把a[3]数组看成是一种元素。
那么就是b包含了两个元素。也就是看成是一维数组b[0]=元素1 b[1]等于元素2。
所以指针就像是这样 元素 *p=b 也就是 int *p[3]=b; (此元素有三个整数所以3,前面加上int 类型)
又因为[]的运算级高于*号,所以int *p[3],你这里是定义了一个指针数组,相当定义了三个指针变量.
这里提一下指针数组和数组的指针 的区别:
那么相当于int a,b,c; p[0]=&a; p[1]=&b; p[2]=&c;
指针数组是这样用的。
所以我们得给*p加上括号,让它成为一个二维数组的指针,即 int (*p)[3];
那么定义二维数组指针指向b就是 int (*p)[3]=b;
而由此可以推断,b+1也就是p+1; 它的跨度是一个a[3]的跨度 12字节。
那么现在我们来实际应用验证一下:
#include <iostream>using namespace std;int main() {int b[2][3];b[0][0] = 1;b[0][1] = 20;b[0][2] = 33;b[1][1] = 66;b[1][2] = 88;//输出二堆数组的每个地址:for (int i = 0; i < 2; i++)for (int j = 0; j < 3; j++)cout << "b[" << i << "][" << j << "]地址:" << &b[i][j]<< endl;//输出b[0]和b[1]地址:cout << "\n\nb[0]地址:" << b[0] << "\nb[1]地址:" << b[1] << endl;//以上为观察对比数据int(*p)[3] = b;cout << "以下为二维数组指针p的用法:" << endl;//取地址的方法:cout << "*p是b[0]的地址,p是b的地址,所以相等(包括&b[0][0]):" << *p << "-------" << p << endl;//取值的方法cout << **p << endl;cout << (*p)[0] << endl;cout << (*p + 1)[1] << endl;cout << p[1][2] << endl;}
结果:
注意这里的(*p+1)[1]指向的是 b[0][2] 输出的33,这是由于运行符优先级的问题。
因为要先运算*p所以得到了b[0]这个类型的地址,然后加1,就只会加4字节。即b[0][1];
然后,再加上一个[1],就会再加4字节,实际跨度就只有8字节,正好是b[0][2]所以输出33.
类似这样:
int a[3] = { 1,20,50 };cout << (a + 1)[1] << endl; //输出的是50 //或者指针的用法int* pa = a;cout << (pa + 1)[1] << endl;//跟上面一样
那么,如果想访问b[1][1]该怎么做,用括号指定优先级,先运算p+1,再取地址,然后[1]。
即:(正确用法)
cout << (*(p + 1))[1] << endl;
那么为什么又不是*(p + 1)[1]呢?我们可以根据优先级来计算,最终这个指向哪个地址,获取的是什么值。
p+1 可以知道,跨度为12字节,因为(p+1)所以先计算,之后[]优先级要高于*,所以 再计算[1],
这又是加1,那么(p+1)的结果仍然是一个跨度12字节的类型,再[1],也就是再加上12字节,之后再*,它就是b[2][0]的地址,即b[2]。
地址是可以确定的,如下实验:
#include <iostream>using namespace std;int main() {int b[3][3];b[2][0] = 100;int(*p)[3] = b;cout << *(p + 1)[1] << endl; //结果 输出b[2][0] 100}
那么这里还有一个问题,同样是地址,为什么加一个*号就取值取呢,为什么不是b[2]的地址呢?
这里我们要明白通过p+1 和p[1]的用法,虽然它们同样是给地址加上了12个字节,关键是[]这个运算是什么意思,也就是p+1返回的是什么,p[1]返回的是什么。
看下例代码:
#include <iostream>using namespace std;int main() {int b[2][3];int(*p)[3] = b;cout << p + 1 << endl;cout << p[1] << endl;}
运行结果:
地址是同样的,但是p+1 是一个二维指针的地址 返回类型是(*p)[3];
而p[1] 加12字节然后会解引一层指针,也就是一维指针 返回类类型是 int *p
所以*(p+1)输出的还是地址,此时已经退化成一维的指针。
而*p[1]则是会直接取值,(你可以将[1]的运算符看作是比p+1还多了一个解引操作,即自动将结果加了一个*)(再次划重点[1]增加的值,是根据前面的指针类型数据来增加的int *p是加4,char 加1
int (*p)[3] 是加12)
所以最终*p[1]就是b[1][0]的值。
那么这里留下个题目(*p)[1]又是哪个值? 如果你能分析出来,那么基本上你对二维数组指针有了一个比较好的了解了。
可以看到这些都是有着严格的区分和应用的,所以二维数组b,是不能用int *p这样来指向的,类型不匹配。
但int *p可以指向b[0],b[1],本质上 b[0]是个一维数组名。那么类推三维数组也是一样的,可以依次拆分,按逻辑分解。这里就不介绍了,感兴趣的可以自行实验,乃至更多维的。
下面补充一些字符串数组的说明(未完待续)