一、一维数组
数组名是第0个元素的指针常量。
下标运算的结果得到其元素的引用。
下标运算需要一个操作数为整型,另一个操作数为地址。
int main(int argc, char* argv[])
{
int ary[5] = { 1, 2 };
// &2[ary] = &ary[2];
printf("%p\r\n", &ary[2]); // 0019FED0
printf("%p\r\n", &2[ary]); // 0019FED0
system("pause");
return 0;
}
一维数组的寻址方式:
type ary[M] = …; ary[n] address is:
(int)ary + sizeof(type) * n
int main(int argc, char* argv[])
{
int ary[2] = { 0 };
// 0x400000处的MZ标识
printf("%p\r\n", (void*)ary[(0x400000 - (int)ary) / sizeof(int)]); // 00905A4D
printf("%s\r\n", (char*)&ary[(0x400000 - (int)ary) / sizeof(int)]); // MZ?
system("pause");
return 0;
}
二、二维数组
二维数组是特殊的一维数组,数组元素是一维数组的数组
int ary[2][3] = {
{1,2,3}, // ary[0]
{4,5,6} // ary[1]
};
// ary有两个元素,每个元素是int[3]类型
int[3] ary[2] = {
{1,2,3},
{4,5,6}
};
// 两次下标运算,第一次下标运算得到ary[1],为一维数组里面3个元素
// 第二次下标运算从ary[1]的一维数组中取第2个元素得到数据
ary[1][2];
二维数组寻址方式
type ary[N][M] = ...;
int x,y = ...;
ary[x][y] address is:
(int)ary + sizeof(type[M])*x + sizeof(type)*y
==> (int)ary + sizeof(type)*M*x + sizeof(type)*y
==> (int)ary + sizeof(type)*(M*x + y)
ary[0][M*x + y] <==> ary[x][y]
三、指针
指针 = 地址 + 解释方式
指针运算
1.指针加整型得到同类型的指针常量
2.对指针作下标运算,得到对指针类型的变量引用
3.同类型指针可以相减,结果为整型常量
type *p = ...;
int n = ...;
// 1.指针加整型得到同类型的指针常量
p + n = (int)p + n*sizeof(type);
// 2.对指针作下标运算,得到对指针类型的变量引用
p[n] = *(type*)((int)p + n*sizeof(type));
type *p1 = ...;
type *p2 = ...;
// 3.同类型指针可以相减,结果为整型常量
p1 - p2 = ((int)p1 - (int)p2) / sizeof(type);
*p++ 与 *++p
*的优先级与++的优先级相同,优先级都为2,且都是从右到左的结合方向。
*p++: 首先执行p++,但后置++是整条语句执行完以后p再++。然后执行 *p取值。最后p+=1。
等同于*p;p+=1;
void mystrcpy(char* szDst, char* szSrc)
{
while(*szDst++ = *szSrc++);
}
*++p:从右向左的结合方向,++p先执行,执行完以后p更新为p+1,然后再取值。
等同于:p+=1;*p;
四、指针数组与二维指针
指针数组:
优点:综合了变长存储和定长存储的优点。有着变长存储的数据大小可变的优点,也有着定长存储的随机访问的优点。排序时只需要交换数组中的指针即可,没有元素的拷贝。变长存储的查找和排序问题得到了解决。
缺点:数据量很大时,插入和删除的开销很大。
int main(int argc, char* argv[], char* envp[])
{
// 命令行以argc个参数作为结尾标志
for (int i = 0; i < argc; i++)
{
puts(argv[i]);
}
// 环境变量以NULL作为结尾标志
while (*envp != NULL)
{
puts(*envp);
envp++;
}
system("pause");
return 0;
}
这里的argv作为数组名,数组名是第0个元素(char * 类型)的指针常量,也就是 char**类型。因此要接收argv需要定义一个二维指针。
int main(int argc, char* argv[])
{
char** p = argv;
system("pause");
return 0;
}
二维指针作为指针的指针,还可以用来作为形参,在函数内对该二维指针做间接访问,来修改实参(一维指针&运算),进而达到修改一维指针的目的。
int g_nTest = 0x123;
// 通过参数传出指针,要传入二级指针
// 传递pn的地址保存在ppn参数变量中
void SetPoint(int** ppn)
{
// 发生间接访问 将pn地址的内容修改
*ppn = &g_nTest;
}
int main(int argc, char* argv[])
{
int n = 0x888;
int* pn = &n;
// 修改pn的指向 设置pn保存全局变量的地址
SetPoint(&pn);
system("pause");
return 0;
}
五、数组指针
考虑一种情况,对于一维数组,如果定义指针接收数组名,没问题,因为类型相同可以将ary直接赋值给p。
int ary[4] = {10,20,30,40};
int * p = ary;
但如果定义如下,则编译会警告。
// warning C4047: “初始化”:“int *”与“int (*)[4]”的间接级别不同
int * p = &ary;
由警告内容可知,对数组名取地址(&ary
)得到的类型是int (*)[4]
类型。为什么会是这种类型?
首先ary
也是定义的变量,只不过其类型为int[4]
类型(int类型的数组,里面4个元素),由&运算可知,对变量做&运算得到同类型变量的指针,所以对变量ary
取地址的得到的类型为int[4] *
所以应该如下定义
int[4] *p = &ary;
但语法上不支持这种写法,正确的写法应该如下
int (*p)[4] = &ary;
这里的括号是区别于指针数组int *p[4]
,因为*的优先级低于[],所以数组指针的写法加了括号。
总结:对数组取地址得到数组指针类型。
int main(int argc, char* argv[], char* envp[])
{
int ary[4] = {10,20,30,40};
printf("%p\r\n", ary);
printf("%p\r\n", &ary); // 数组取地址得到数组指针 int[4]*
printf("%p\r\n", ary + 1);
printf("%p\r\n", &ary + 1); // &ary为int[4]*类型。为int[4]类型的指针,由指针运算方式可知,&ary+1的地址为 (int)&ary + sizeof(int[4])*1 = (int)&ary+16
system("pause");
return 0;
}
/*
// output
0019FECC
0019FECC
0019FED0
0019FEDC
0x0019FECC 0a 00 00 00 14 00 00 00 1e 00 00 00 28 00 00 00 ............(...
0x0019FEDC cc cc cc cc 56 98 37 56 04 ff 19 00 63 74 45 00 ????V?7V....ctE.
0x0019FEEC 01 00 00 00 00 6c 92 00 08 93 92 00 01 00 00 00 .....l?..??.....
*/
再考虑一种情况,能否定义一个变量用来接收二维数组的数组名?
int nAry[2][4] = {
{10,20,30,40},
{60,70,80,90
};
该数组的类型是int[4]类型,nAry
有两个元素,每个元素是一个int[4]
也就是int类型的数组。
数组名是第0个元素的指针常量,数组名nAry
的第0个元素nAry[0]
为int[4]
类型,所以nAry是int[4]*
类型。
因此可以这样定义变量
int[4] *p = nAry;
同样由于语法不支持,正确写法应该是
int (*p)[4] = nAry;
int main(int argc, char* argv[])
{
int nAry[2][4] = {
{10,20,30,40},
{60,70,80,90}
};
int(*p)[4] = nAry;
system("pause");
return 0;
}
小试牛刀
int main(int argc, char* argv[])
{
int nAry[2][4] = {
{10,20,30,40},
{60,70,80,90}
};
int(*p)[4] = nAry;
/*
0x0019FEBC 0a 00 00 00 14 00 00 00 1e 00 00 00 28 00 00 00 ............(...
0x0019FECC 3c 00 00 00 46 00 00 00 50 00 00 00 5a 00 00 00 <...F...P...Z...
0x0019FEDC cc cc cc cc 85 c9 90 ad 04 ff 19 00 c3 75 45 00 ????????....?uE.
0x0019FEEC 01 00 00 00 00 6c 8a 00 08 93 8a 00 01 00 00 00 .....l?..??.....
*/
printf("%p\r\n", p);
printf("%p\r\n", *p);
printf("%p\r\n", (void*)**p);
printf("%p\r\n", (void*)sizeof(p));
printf("%p\r\n", (void*)sizeof(*p));
printf("%p\r\n", (void*)sizeof(**p));
printf("%p\r\n", p + 1);
printf("%p\r\n", *p + 1);
printf("%p\r\n", (void*)(**p + 1));
printf("%p\r\n", p[1] + 1);
printf("%p\r\n", (void*)*((p + 1)[1]));
printf("%p\r\n", (void*)(*(p + 1))[1]);
// 下标运算的优先级比*运算的优先级高
printf("%p\r\n", (void*)(*p[1] + 1));
printf("%p\r\n", (void*)(p[1] + 1)[1]);
system("pause");
return 0;
}
/*
0019FEBC
0019FEBC
0000000A
00000004
00000010
00000004
0019FECC
0019FEC0
0000000B
0019FED0
CCCCCCCC
00000046
0000003D
00000050
*/
总结
序号 | 规则 |
---|---|
0 | 数组名是数组第0个元素的指针常量 |
1 | 数组取下标运算得到其元素的引用 |
2 | 某类型变量&运算得到同类型的指针 |
3 | 某类型指针加整型得到同类型的指针常量 |
4 | 某类型指针作下标运算得到同类型的变量引用 |
5 | 某类型指针*运算得到某类型变量的引用 |