C语言数组与指针

一、一维数组

数组名是第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某类型指针*运算得到某类型变量的引用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shlyyy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值