目录
C语言只有一维数组,而且数组的大小必须在编译期就作为一个常数确定下来。然而,C语言中数组的元素可以是任意类型的对象,当然也可以是另一个数组。这样,要“仿真”出一个多维数组就不是一件难事。
对于一个数组,我们只能做两件事:确定数组的大小,获得指向该数组下标为0的指针。
其它有关数组的操作,都是通过指针进行的。换句话说,任何一个数组下标的运算都等同于一个对应指针的运算,因此我们完全可以依据指针行为定义数组下标的行为。——《C缺陷与陷阱》
- int main()
- {
- int a[5] = { 1, 2, 3, 4, 5 };
- int* ptr = (int*)(&a + 1);
- printf("%d,%d", *(a + 1), *(ptr - 1));
- return 0;
- }
- //程序的结果是什么?
首先我们看(&a+1)
其中,a是数组名,那我们就知道,在数组名前加取地址符,表示整个数组的地址。
而&a+1表示的地址,则是跨过整个数组的第一个地址。(如果不明白,请看下方例题说明图)
此时我们可以知道&a+1表示的地址的位置。
此时&a+1的类型为int (*)[5]:
a为数组首元素,类型为int [5],取地址后,为数组指针类型int (*)[5]。
将&a+1强制类型转换为和指针变量ptr相同的类型,在赋给它。
*(a+1):a是数组名,表示首元素地址,加1表示增加一个整型的空间,指向下标为1的数组的,解引用后值为2。
*(ptr-1):由图可知,指针变量ptr此时指向a[5](此时数组以越界,方便引用所以如此写,这种写法是错误的),ptr-1表示减去一个整型的地址,指向下标为4的数组,解引用后为5.
- struct Test
- {
- int Num;
- char* pcName;
- short sDate;
- char cha[2];
- short sBa[4];
- }*p;
- //假设p 的值为0x100000。 如下表表达式的值分别为多少?
- //已知,结构体Test类型的变量大小是20个字节
- int main()
- {
- p = (struct Test*)0x100000;
- printf("%p\n", p + 0x1);
- printf("%p\n", (unsigned long)p + 0x1);
- printf("%p\n", (unsigned int*)p + 0x1);
- return 0;
- }
- //最终的输出结果为?
我们可以得到信息,p的地址为00100000,此结构体的大小为20个字节。
p+0x1:表示p+1,指针加减整数,看指针的类型的大小加减地址,此结构体类型的大小为20,所以增加20,而地址的表示方法是16进制,增加20即增加14,最终结果为00100014。
(unsigned long)p+0x1:将结构体指针类型强制类型转换为unsigned long类型,不是指针,加减正常,结果为00100001。
(unsigned int*)p+0x1:由上可知,此次需要增加一个整型的大小,即+4,结果为00100004。
- int main()
- {
- int a[4] = { 1, 2, 3, 4 };
- int* ptr1 = (int*)(&a + 1);
- int* ptr2 = (int*)((int)a + 1);
- printf("%x,%x", ptr1[-1], *ptr2);
- return 0;
- }
- //结果为多少?
如图为数组a在内存中的存储图
我们可以将它简化为:
&a+1:增加一个数组a的大小
(int*)(&a+1):将其转化为int*类型在赋给指针变量ptr。
(int)a+1:将首元素的地址转化为int类型,因为此时不是指针,直接加1即可,即假设a的地址为00000001,直接加1为00000002,因为数组的地址是相连的,此时这个值,实际表示下表为0的数组所占四个字节中的第二个字节。强制类型转化后,ptr2指向此位置。
如图所示:
ptr1[-1]:即ptr1指向位置的前一个整型的地址,a[3],结果为4
*ptr2:解引用后,为整型,占四个字节,(该实验基于VS编译器,VS编译器为小端),即从左向右的四个字节(图中红框以标出),依次从小到大。它的十六进制表示为0x02000000。
题中要求打印十六进制数:
- %x:是16进制的格式打印,并消出高位多余0.
- #include
- int main()
- {
- int a[3][2] = { (0, 1), (2, 3), (4, 5) };
- int* p;
- p = a[0];
- printf("%d", p[0]);
- return 0;
- }
二维数组内,(0,1),(2,3),(4,5)为逗号表达式,取逗号右边的,即1,3.,5,
所以此二维数组为:
int a[3][2] = { 1,3,5 };
则p[0]为1.
- int main()
- {
- int a[5][5];
- int(*p)[4];
- p = a;
- printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
- return 0;
- }
p为数组指针变量,p = a即为 int (*p)[4] = a;
*(p+0) = a[0]; (*(p+0))[0] = a[0][0]; (*(p+0))[3] = a[0][3];
*(p+1) = a[1]; (*(p+1))[0] = a[1][0]; (*(p+1))[3] = a[1][3];
....
*(p+4) = a[4]; (*(p+4))[0] = a[4][0]; (*(p+4))[3] = a[4][3];
因为此数组指针的含义为存储4个整型的大小的一维数组,
而数组a由5个存储大小为5的一位数组的二维数组组成。
所以可以得到如图表示:
故&p[4][2] - &a[4][2]中间相差4个元素,而&p[4][2]为低地址处所以为-4。
第一个输出的形式为%p,十六进制,-4转化为2进制为
10000000000000000000000000000100 //原码
111111111111111111111111111111111100 //补码
转化为十六进制为:FFFFFFFC
第二个输出整型,-4
- int main()
- {
- int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
- int* ptr1 = (int*)(&aa + 1);
- int* ptr2 = (int*)(*(aa + 1));
- printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
- return 0;
- }
二维数组的本质是一维数组,比如此题的数组aa表示数组内含有两个元素,分别是两个一位数组,
而这两个一维数组又分别包含五个元素。
&aa+1:此时aa表示整个数组的地址,加1为增加整个数组所占的空间的大小。
*(aa+1):aa为数组首元素的地址,加1后aa+1为数组第2个元素的地址,也就是第二个一维数组的地址。
ptr1和ptr2都为指针,指针减整型,减去存储内存的元素的类型。
ptr1-1:减去一个整型的空间,指向aa[1][4]
ptr2-1:同上,指向aa[0][4]
最终结果为10,5
- #include
- int main()
- {
- char* a[] = { "work","at","alibaba" };
- char** pa = a;
- pa++;
- printf("%s\n", *pa);
- return 0;
- }
char* a[]为指针数组,是数组用来存放指针,该数组有三个元素,分别是三个字符串的地址。
pa = a;是将数组a的首元素的地址放入二级指针变量pa中。
pa++:即为a++,从数组首元素的地址变为第二个元素的地址。
*pa:此时pa为a[1],也就是第二个元素表示字符串的首地址,以%s的形式输出,依次打印字符串。
- #include
- int main()
- {
- char* c[] = { "ENTER","NEW","POINT","FIRST" };
- char** cp[] = { c + 3,c + 2,c + 1,c };
- char*** cpp = cp;
- printf("%s\n", **++cpp);
- printf("%s\n", *-- * ++cpp + 3);
- printf("%s\n", *cpp[-2] + 3);
- printf("%s\n", cpp[-1][-1] + 1);
- return 0;
- }
数组c中共有4个元素,分别为四个字符串的地址。
数组cp中共有4个元素,分别是存储数组c中4个字符串地址的4个地址
指针cpp存储数组cp中首元素的地址。
我们可以得到如图的结构