指针数组与数组指针简明分析


更新日志:
2023-08-01 更新文章错误。对数组指针赋值错误。优化若干细节。

前言

该文章主要使用例子论证数组指针与指针数组;
希望阅读文章的小伙伴详细阅读并使用运行或自行编写相关例程加深印象。
文章一气呵成,人脑资源有限我能想到的情况大致如下,如有错误不足欢迎批评指正互相学习。
受编译器即机器的不同,运行打印地址的结果会有不同,请读者自行区分

一、指针数组

如例子int *p[5]为一个指针数组,为什么为指针数组让我们将其分解研究:
a. 首选“[ ]”优先级高于“ * ” ===》》》推断它是一个数组。
b.int * p1[5] 接合性从左向右 ===》》》 推断它是一个int *型的指针。
c.p1[5]那么指针数组的名字为p1结合b结论 ===》》》数组各个元素为int型指针。

在这里插入图片描述

例一:证明p为指针数组的数组名

#include <stdio.h>
 
int main(void)
{
    int *p[5]={NULL};
    int arr[5]={0};
    p=arr;//此处错误对一个数组名进行赋值
    printf("%d--%d--%d",arr,p,*p);
    return(0);
}

运行结果:编译错误,错误为对一个数组名进行赋值

分析:数组名为常量,数组名是在编译后由系统进行赋值不可二次改变,因此将arr地址值赋值给p是错误的,p不可被改变

例二:正确的使用与赋值

#include <stdio.h>
 
int main(void)
{
    int *p[5]={NULL};
    int arr[5]={0};
    for(int i=0;i<5;i++)
    {
        p[i]=&arr[i];
        //*(p+i)=arr+i;
    }
    printf("arr:\t%p\r\n",arr);
    printf("*p:\t%p\r\n",*p);
    printf("p:\t%p\r\n",p);
    return(0);
}

运行结果
arr: 0x7ffd829ca720
*p: 0x7ffd829ca720
p: 0x7ffd829ca740

分析
由此可见*p与arr即arr[5]数组的首地址相同因此可总结为
*p==&arr[0]
*p==arr

例三:对arr数组值打印操作(错误示范)

#include <stdio.h>
 
#include "stdio.h"
int main(void)
{
    int *p[5]={NULL};
    int arr[5]={0,1,2,3,4};
    p=arr;//此处类型不兼容
    printf("%d\r\n",p[1]);//错误示范
    return(0);
}

运行结果
编译报错

分析
p[5]每个成员都是int 类型的指针,如果复制成功那么p[0]==*arr[0]?因此arr与p不是一个类型。同理p=&arr可行?arr已经是一个一维指针了无法再对arr进行取址了。此处可以使用一个for循环进行逐一赋值。

例四:对arr数组值打印操作(正确示范)

#include "stdio.h"
int main()
{
    int *p[5]={NULL};
    int arr[5]={0,1,2,3,4};
    for(int i=0;i<5;i++)
    {
         //p[i]=&arr[i];//a.此处可使用两种赋值方法
          *(p+i)=arr+i;//b方法
    }
    printf("arr:%d--%d--%d--%d--%d\r\n",arr[0],arr[1],arr[2],arr[3],arr[4]);
    printf("*arr:%d--%d--%d--%d--%d\r\n",*arr,*(arr+1),*arr+2,*arr+3,*arr+4);
    printf("*p[x]:%d--%d--%d--%d--%d\r\n",*p[0],*p[1],*p[2],*p[3],*p[4]);
    printf("*p+i:%d--%d--%d--%d--%d\r\n",**p,**p+1,**p+2,**p+3,**p+4);
    return(0);
}

运行结果
arr:0–1–2–3–4
*arr:0–1–2–3–4
*p[x]:0–1–2–3–4
*p+i:0–1–2–3–4

分析
arr == p[0];
arr+1 == p[1];
*arr == arr[0] == **p==*p[0];
*arr+1 == arr[1] == **p+1==*p[1];

例五:指针数组地址打印

#include "stdio.h"
int main()
{
    int *p[5]={NULL};
    int arr[5]={0,1,2,3,4};
    for(int i=0;i<5;i++)
    {
         //p[i]=&arr[i];
          *(p+i)=arr+i;
    }
    printf("%p--%p--%p--%p\r\n",&arr[0],&arr[1],&arr[2],&arr[3]);
    printf("%p--%p--%p--%p\r\n",arr,arr+1,arr+2,arr+3);
    printf("%p--%p--%p--%p\r\n",p[0],p[1],p[2],p[3]);
    printf("%p--%p--%p--%p\r\n",*p,*p+1,*p+2,*p+3);
    return(0);
}

运行结果
0x7ffd76097940–0x7ffd76097944–0x7ffd76097948–0x7ffd7609794c
0x7ffd76097940–0x7ffd76097944–0x7ffd76097948–0x7ffd7609794c
0x7ffd76097940–0x7ffd76097944–0x7ffd76097948–0x7ffd7609794c
0x7ffd76097940–0x7ffd76097944–0x7ffd76097948–0x7ffd7609794c

分析
&arr[0] ==arr == p[0] == *p;
&arr[1] == arr == p[1] == *p+1;

例六:指针数组与二维数组

#include "stdio.h"

int main(void)
{
    int *p[2]={NULL};//例为存储二维数组行指针
    int arr[2][5]={{0,1,2,3,4},{5,6,7,8,9}};
    for(int i=0;i<2;i++)
    {
        *(p+i)=*(arr+i);//以下三种方法赋值会得到相同结果
        //p[i]=arr[i];
         //p[i]=&arr[i][0];
    }
    printf("********addr **************\r\n");
    printf("%p--%p\r\n",arr[0],arr[1]);//以16进制答应行0与行1首地址
    printf("%p--%p\r\n",arr,arr+1);//以16进制打印行0与行1首地址 注意分析arr与arr[0]是否有区别?
    printf("%p--%p\r\n",*arr,*(arr+1));//以16进制答应行0与行1首地址
    printf("%p--%p\r\n",p[0],p[1]);//以16进制答应行0与行1首地址
    printf("%p--%p\r\n",*p,*p+1);//以16进制答应行0与行1首地址
    printf("%p--%p\r\n",p[0]+1,p[1]+1);//以16进制答应0行1列的地址与1行1列的地址

    printf("******** cvalue**************\r\n");
    printf("%d--%d--%d--%d\r\n",arr[0][0],arr[0][1],arr[1][0],arr[1][1]);//直接打印部分数组值
    printf("%d--%d--%d--%d\r\n",*p[0],*(p[0]+1),*p[1],*(p[1]+1));//用指针数组方式打印部分值
    printf("%d--%d--%d--%d\r\n",p[0][0],p[0][1],p[1][0],p[1][1]);//用指针数组方式打印部分值
    return(0);
}

运行结果
********addr ***********
0x7ffee241f7c0–0x7ffee241f7d4
0x7ffee241f7c0–0x7ffee241f7d4
0x7ffee241f7c0–0x7ffee241f7d4
0x7ffee241f7c0–0x7ffee241f7d4
0x7ffee241f7c0–0x7ffee241f7c4
0x7ffee241f7c4–0x7ffee241f7d8
******** cvalue**************
0–1–5–6
0–1–5–6
0–1–5–6

分析
*(p+i) == arr[i] == p[i] == * (arr+1) == &arr[i][0]
arr[0][1] == p[0][1] == **p+1 == *(p[0]+1)

此例程中引入了一个疑问,arr与arr[0]是否相同? 从程序的结果看arr与arr[0]的地址都是相同的结果,但是注意对于编译器来说它们是不相同的。


1.arr指向二维数组的开头的,指向的是二维数组 数组类型为 int [][5];
2.arr[0]指向二维数组的0行说白了任然是个一维数组,数组类型为 int [];
3.此时指针的一个特点必须为相同类型的指针之间才能进行赋值操作,因此如果你是初始化int *a=arr编译器会报assignment from incompatible pointer type;
如果是初始化为int *a=arr[0] 则不会报警告内容;
置于arr初始化赋值我们引入数组指针一并分析;

二、数组指针

数组指针可以如此来理解:例int (* p)[5]
a.以优先级为例初始化是为从左到右的结合性因此先() 再*p2,最后[5] 因此为数组指针。
b.它指向了含有n个int类型的数组(指向数组的指针)。
在这里插入图片描述

例一:证明*p为数组指针的数组名

#include <stdio.h>
 
int main(void)
{
     int (*p)[5]={0};
     int arr[2][5]={{0,1,2,3,4},{5,6,7,8,9}};
     *p=arr;
     return 0;
}

运行结果:编译错误,错误为对一个数组名进行赋值

分析:数组名是在编译后由系统进行赋值不可二次改变,因此将arr地址值赋值给p是错误的,p不可被改变 因此数组名为*p这与指针数组一个区别;

例二:正确的使用与赋值

#include <stdio.h>
 
int main(void)
{
     int (*p)[5]={0};
     int arr[2][5]={{0,1,2,3,4},{5,6,7,8,9}};
     printf("%#x--%#x",arr[0],arr);
    //p=arr[0];//错误赋值项
    p=arr;
    return 0;
}

运行结果
0xda6e9a80–0xda6e9a80

分析
arr的地址值与arr[0]的值相同,那么为什么呢?其实还是在指针数组例六提到的问题,就是类型不匹配。
a.在数组与指针中 []与取值符*可以等价替换那么数组指针就等价与p[][5];
b.此时p是不是一个二维数组那么arr[0]一个指向行的一维数组赋值给二维数组是不正确的;

例三:地址与值引用

#include <stdio.h>

int main(void)
{
     int (*p)[5]={0};
     int arr[2][5]={{0,1,2,3,4},{5,6,7,8,9}};
    p=arr;
    printf("*************addr*****************\r\n");
    printf("%p--%p--%p--%p\r\n",arr,arr+1,arr[0],arr[1]);
    printf("%p--%p--%p--%p\r\n",&arr[0][0],&arr[1][0],*arr,*(arr+1));
    printf("%p--%p--%p--%p\r\n",p,p+1,p[0],p[1]);
    printf("%p--%p--%p--%p\r\n",&p[0][0],&p[1][0],*p,*(p+1));

    printf("*************value*****************\r\n");
    printf("%d--%d--%d--%d\r\n",arr[0][0],arr[0][1],arr[1][0],arr[1][1]);
    printf("%d--%d--%d--%d\r\n",*arr[0],*arr[0]+1,*arr[1],*arr[1]+1);
    printf("%d--%d--%d--%d\r\n",**arr,**arr+1,**(arr+1),**(arr+1)+1);
    printf("%d--%d--%d--%d\r\n",p[0][0],p[0][1],p[1][0],p[1][1]);
    printf("%d--%d--%d--%d\r\n",*p[0],*p[0]+1,*p[1],*p[1]+1);
    printf("%d--%d--%d--%d\r\n",**p,**p+1,*(*p+1),**(p+1)+1);
    return 0;
}

运行结果
*************addr*****************
0x35c16ad0–0x35c16ae4–0x35c16ad0–0x35c16ae4
0x35c16ad0–0x35c16ae4–0x35c16ad0–0x35c16ae4
0x35c16ad0–0x35c16ae4–0x35c16ad0–0x35c16ae4
0x35c16ad0–0x35c16ae4–0x35c16ad0–0x35c16ae4
*************value*****************
0–1–5–6
0–1–5–6
0–1–5–6
0–1–5–6
0–1–5–6
0–1–1–6

分析
(*p)[5]==arr[2][5]==p[][5];
printf(“%#x–%#x–%#x”,*p,p,arr);三者地址值相同,但是不可进行 p=arr的操作p是数组地址常量。
*p+1 == p[1] == arr[1] == *arr+1 此处指向一行首地址
** p+1 == p[1][0] == arr[1][0] == **arr+1 ==*arr[1] ;
剩余自行总结;

例四:数组指针与一维数组

#include <stdio.h>

int main(void)
{
     int (*p)[5]={0};
     int arr[5]={0,1,2,3,4};
    p=&arr;//由于数组指针为int (*)[x]类型因此将arr赋值域*p类型需要进行取址
    printf("*************addr*****************\r\n");
    printf("%p--%p--%p--%p\r\n",arr,arr+1,arr+2,arr+3);
    printf("%p--%p--%p--%p\r\n",&arr[0],&arr[1],&arr[2],&arr[3]);
    printf("%p--%p--%p--%p\r\n",p,p+1,p[0],p[1]);//p行指针 p[0]列指针
    printf("%p--%p--%p--%p\r\n",&p[0][0],&p[1][0],*p,*(p+1));

    printf("*************value*****************\r\n");
    printf("%d--%d--%d\r\n",arr[0],arr[1],arr[2]);
    printf("%d--%d--%d\r\n",*arr,*arr+1,*arr+2);
    printf("%d--%d--%d--%d\r\n",p[0][0],p[0][1],p[0][2],p[0][3]);
    printf("%d--%d--%d--%d\r\n",*p[0],*p[0]+1,*p[0]+2,*p[0]+3);
    printf("%d--%d--%d--%d\r\n",**p,**p+1,*(*p+1),**p+2);//不建议采用**p+1这样写建议使用()区分优先级及结合性
    return 0;
}

运行结果
*************addr*****************
0x7ffe0d9e90b0–0x7ffe0d9e90b4–0x7ffe0d9e90b8–0x7ffe0d9e90bc
0x7ffe0d9e90b0–0x7ffe0d9e90b4–0x7ffe0d9e90b8–0x7ffe0d9e90bc
0x7ffe0d9e90b0–0x7ffe0d9e90c4–0x7ffe0d9e90b0–0x7ffe0d9e90c4
0x7ffe0d9e90b0–0x7ffe0d9e90c4–0x7ffe0d9e90b0–0x7ffe0d9e90c4
*************value*****************
0–1–2
0–1–2
0–1–2–3
0–1–2–3
0–1–1–2

分析
a.首先不建议采用 * * p+1这样的写法,建议采用()区分结合进行计算。

b.(* p)[5]为一个二维数组p[][5],那么arr赋值给p 则 * p即为p[0]为什么不是arr[0]的值呢
解答:* p是一个二维数组指针指向二维的数组0行的首地址,因此 * p的地址与p的地址相同但是类型不同(一个是一维一个是二维),因此 对二维再取值才是一维的值 即为
*( *p+1) == *(*arr+1) == arr[1] == p[0][1];

c.注意看地址打印结果验证上诉说法
p+1地址从p增加了0x14即20个地址,因此p为二维指针及行地址
也可以尝试打印
printf(“%#x–%#x\r\n”, p[1][0],**(p+1));
由于越界越界到1行位置及arr[5]的起始位置,因此打印结果为垃圾值

在这里插入图片描述

三、总结

a.指针不仅要看数据类型如 int char float …等 还要看其指针类型如一维二维等。
b.千万注意数组名为数组地址常量不可赋值。
c.指针数组多用于指向一维数组,数组指针多用于二维数组处理(上例中可以很清楚的用的很乱)
d.多注意等价替换 例如 [ ]==* ;
e.写指针时不清楚如何结合优先级问题等,不如使用()辅助例如*(*p+1) ==p[0][1];

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 《C程序设计原理与实践 第2版pdf》是一本关于C语言程序设计的教材,旨在帮助读者系统学习C语言的基本原理和实践技巧。本书由许莹(Ying Bai)编写,共分为11个章节,内容详实且易于理解。 第一章介绍了C语言的概述,包括其发展历史、特点和应用领域。第二章主要讲解C语言的基本语法和语句结构,如变量、数据类型、运算符、控制结构等。 第三章至第七章侧重于C语言的高级编程技巧和常用函数的使用。这些章节讲解了C语言中的数组指针、字符串、结构体和文件操作等重要概念和技术。 第八章到第十一章则介绍了C语言的进阶主题,如动态内存分配、多文件编程、位操作和预处理器等。此外,还有一章专门讲解了C语言在面向对象编程中的应用。 本书的优点在于理论与实践相结合,既有详实的示例和实验,又有深入的理论解析和编程技巧分享。读者可以通过实践中的编程练习,巩固所学知识。同时,书中还提供了大量的练习题和实践项目,可供读者进一步提升编程水平。 总的来说,《C程序设计原理与实践 第2版pdf》是一本全面系统的C语言学习指南,适合初学者和有一定编程经验的读者。通过学习这本教材,读者能够全面掌握C语言的基本原理和实践技巧,为日后的软件开发和编程工作打下坚实基础。 ### 回答2: 《C程序设计原理与实践 第2版》是一本介绍C程序设计原理和实践的书籍,是C语言学习者的必备教材。该书第2版的PDF版本提供了电子版的阅读方式。 该书主要分为三大部分:基本概念与技巧、进阶应用和常用库函数。在基本概念与技巧部分,作者详细介绍了C语言的基本语法、数据类型、运算符、流程控制等内容,帮助读者建立扎实的基础。进阶应用部分则介绍了C语言的高级特性和编程技巧,包括函数、指针数组、结构体、文件操作等,使读者能够更灵活地运用C语言进行程序开发。常用库函数部分则列举了C语言常用的标准库函数,如字符串处理、数学计算、输入输出等,方便读者在实际编程中使用。 此书与其他C语言教材相比,具有简明易懂、内容全面深入的特点。作者通过实例和练习题的方式,帮助读者巩固知识点并能够灵活运用。此外,书中还提供了一些编程实践的经验和技巧,帮助读者提高编程效率和质量。 第2版的PDF版本提供了电子化的阅读方式,方便读者在电脑、平板或手机上进行学习。电子版的优势在于便携性,读者可以随时随地进行学习。同时,电子版还具有可搜索、可标注、可复制等功能,方便读者进行查找和学习记录。 总而言之,《C程序设计原理与实践 第2版》PDF提供了一种便捷的学习方式,内容丰富且易懂。无论是初学者还是有一定编程基础的读者,都能够从中受益。 ### 回答3: 《C程序设计原理与实践 第2版》pdf是一本介绍C编程的教材,该教材的第2版是在第1版的基础上进行完善和更新的。 该教材主要分为两个部分,即程序设计原理和程序设计实践。在程序设计原理部分,教材详细介绍了C语言的基本概念和语法,包括数据类型、运算符、控制结构等。同时,还介绍了常见的编程错误和调试技巧,帮助读者养成良好的编程习惯。 在程序设计实践部分,教材通过大量的实例演示如何运用C语言进行实际的程序设计。这些实例包括计算机图形学、游戏开发、网络编程等方面,读者可以通过学习这些实例,了解C语言在不同领域的应用。 此外,教材还提供了大量的练习题和实践项目,帮助读者巩固所学知识并提高编程能力。教材中还包含了一些实用的编程工具和技巧,帮助读者提高程序的效率和质量。 总的来说,《C程序设计原理与实践 第2版》pdf是一本全面介绍C编程的教材,适合初学者和有一定基础的读者阅读。通过学习这本教材,读者能够系统地学习C语言的基础知识和编程技巧,提高自己在程序设计方面的能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值