[题集]指针与数组

陈述下列语句的意义

(1)int(*p)[11];

因为括号优先级最大,则p先与*结合,指向一个二维数组,为一个指针数组,p的值是数组的首地址

(2)int *p[11];

一个指针类型的数组,数组内的每一项数据都为指针类型

(3)int **p

一个二维指针,指向指针的指针,通常它的值为以为指针的地址,通过调用改变它的值可以改变一维数组的指向

有一个语句如下:

  • char str[][15]={“Stanford”,“University”,“California”};

试说明为什么str+1和*(str+1)所代表的意义是一样的呢?

  • 数组定义为char类型,行由计算机自动分配,每一行有十五个字节长度,包含三个字符串,理应为str3

str+1的意义为str[1]应输出University首字母U的地址

  • *(str+1)是数组的首地址+1,括号优先级最大,指向下一个地址,再取值,输出也是University首字母U的值

由于n是一个二维数组,需以两个*或者两个[ ] 或者一个*搭配一个[ ]才能的到元素的值

有一个二维数组声明如下:

  • short int n[][3]={10,20,30,40,50,60,70,80,90};

试问下列各项表达式的输出结果是什么?是数组元素的地址(address)还是数组元素的值(value)?(假设n数组的起始地址为1010)

解析:由于数组类型为短整型,则一个元素长度为2,数组总长度为18,每行长度为6,起始地址为1010,以此类推,1010,1012,1014,1018,10110,10112,10114,10116,10118

img经测试,预测结果无错

  • n //输出应为数组元素的首地址,也就是10的地址,由题得1010
  • *n //输出应为数组元素首地址的值,也就是1010

由于n是一个二维数组,需以两个*或者两个[ ] 或者一个*搭配一个[ ]才能的到元素的值

  • **n //二级指针可理解为取一级指针的值,由于未进行其他定义,则输出结果也应为10

img经过测试,预测结果无错

  • n[2] //输出第三行的首地址,也就是n2的地址

img经过测试,预测结果无错

  • n2 //输出n2,也就是第二行第一列的值,输出应为80
  • *n[2]+1 //由于*运算符大于+运算符,*与[]优先级相同,顺序由右向左运算,则n先与[2]结合,再取值,然后+1,则是地址加一,输出的值应为第二行第一列50的地址,应题目要求为10112
  • *(n+1)//括号优先级最大,n先与+结合再取值,则输出应为n第一行的首地址,也就是40的地址

img测试结果,与预算无误

由于n是一个二维数组,需以两个*或者两个[ ] 或者一个*搭配一个[ ]才能的到元素的值

  • **(n+2) //上述做了那么多的强调,这儿看到二维数组的两个指针就应该很明白了嘛!就是n2,输出的为70,不信?我们来测试一下!

img

由于n是一个二维数组,需以两个*或者两个[ ] 或者一个*搭配一个[ ]才能的到元素的值

  • n[2]+2 //[ ]运算符比+运算符要高,所以先指向数组第二行+2就是地址前进2*2个字节,输出结果应为n2的地址,还是那句话

由于n是一个二维数组,需以两个*或者两个[ ] 或者一个*搭配一个[ ]才能的到元素的值

测试一下:

img

  • *(n[1]+2) //重复那么多次,一眼看出是*与[ ]结合,输出的应为值,括号优先级最大,先执行n[1]+2=n1的地址,再取值,应为60
  • *(*n+2) //一样的,两个*运算符,括号优先级最大,先看括号里的,n先与*结合,取到n[0]的行地址,再+2取到n0的地址,再与括号外的*结合取值,应为30,题目最后再测试测试吧!

img经过测试,预算结果没错,思路基本正确

这道题我觉得是对掌握数组指针运算的最好的题目,最好记住 !

由于n是一个二维数组,需以两个*或者两个[ ] 或者一个*搭配一个[ ]才能的到元素的值

假设存在如下声明:

  1. char *c[]={"enter","new","point","first"};
  2. char **cp[]={c+3,c+2,c+1,c};
  3. char *cpp=cp;

在做题的时候,我们先来分析题目的的意思

img可能解释比较模糊,不定时修改

然后看看题目要求:

  • 试问下列各项输出结果是什么(以%s输出,每一小题均存在连贯性)
  • **++cpp
  • --++cpp+3
  • *cpp[-2]+3
  • cpp-1+1

第一题:“**++cpp”运算分析:自增与取值运算符优先级相等,运算顺序由右向左

  1. cpp指向cp的首地址,先与++结合,在cp首地址上加一个char*长度,指向cp[1]
  2. 再结合第一个指针取到cp[1]的值,为c+2
  3. 再结合第二个指针取到c+2的值,为c[2],最后输出结果为point

img测试结果

第二题:--++cpp+3,的运算分析,自增自减运算符和指针运算符优先级相等由右向左,最后考虑加法运算

  1. cpp在上题指向了cp[1],这题先与自增结合指向cp[2]
  2. 再结合第一个指针运算,取值cp[2]得到c+1,也就是为c[1]
  3. 再结合自减运算符得到c[0],再结合指针运算取值得到enter
  4. 最后运算加法,从第三列开始输出到0结束,输出结果为er

img测试结果

第三题:重头戏,*cpp[-2]+3,数组下标不允许有负数出现,咱们转换成看得懂的表达式:*(*(cpp-2))+3

  1. 括号优先级最大,cpp在上一题指向了cp[2],可以转换成cp+2,在这一题先与最里面的cpp-2结合,这时它在cpp指向的地址上减2,这时他指向了cp[0]
  2. 再结合中间括号的指针运算符,取到c+3
  3. 再结合最外面的指针运算符,取到c[3]的值为first
  4. 最后结合加法运算,在字符首地址加上3字节,到r开始,输入结果为st

img测试结果

第四题:cpp-1+1更加重头戏,一样,转换成看得懂的表达式*(*(cpp-1)-1)+1

  1. 还是一样,在cpp指向的cp[2]中进行了减一操作,这时指向了cp[1]
  2. 再结合第一个指针运算符,取了cp[1]的值也就是c[2]
  3. 再与外括号的-1结合,在c[2]的地址上减了一,这时它指向了c[1]
  4. 再结合第二个指针运算符,取到了c[1]的值也就是new
  5. 最后结合加法运算符,表示将new这个字符串从下标1开始,作为首地址传给了printf,默认是0开始,n被跳过,输出为er

img测试结果

题目总结:这一题加深了我对[]和*的理解,我卡在了cpp++与cpp+1的这个问题绕不过来,我一直把第三题和第四题连贯起来,也不懂第三题为什么会输出st,其实在cpp的地址上进行运算的只有第一题和第二题的前置自增运算符,++cpp是在cpp的首地址上跨越一个内存,而cpp[-2]是取了cpp指向cp的值上进行操作,一个是直接操作cpp的指向,而另一个是在它指向的值上进行操作,虽然操作的都是cp的地址,但是前者会影响cpp的指向,而后者却不会影响cpp的指向。

有一段程序如下:

  1. int arr[]={100,200,300,400,500};
  2. int *ptr=arr+1;

并假设arr数组的起始地址为22ff50,而ptr的内存地址为22ff4c,试回答下列题目,并注明是地址还是值。

arr[2]= arr为数组名,2是数组下标,语义为取arr[2]的值,输出结果为300,输的的是值

img测试结果,思路正确

*(arr+2)=先与括号内的结合,在arr的首地址上加2获得arr[2]的地址,再取arr[2]的值,输出的为值,300

img经测试,结果一样

arr+2=在arr的首地址上加2个int字节,输出的为地址,由题意首地址为22ff50,加4*2=8得到22ff58

img思路正确

ptr+2=在ptr的首地址上加2个int*字节,,输出的为地址,已知ptr的首地址为22ff54,加上4*2=8得到,22ff5c

*(ptr+2)=先计算括号内的ptr+2,在地址上加上两个int*字节,已知初值为22ff54(arr+1的地址),加2等于22ff5c,取到它的值,输出的为值,400

ptr[0]=取到ptr+2的值,为arr[1]=200

*ptr+2和*(ptr+2)意义一样吗?试说明。

  • 不一样的,前者先取到ptr的值,也就是arr[1]的值为200,再与2相加,得到202
  • 后者则先在ptr的值上加2,则为arr[1]的地址上加上两个int类字节,输出为400

*ptr++和*++ptr意义一样吗?试说明。

不一样,前者由于*与++优先级相等,由右向左先与++结合,得到arr[2],再取值,为300,但是++为后置,则该语句输出的还是为200,在下一语句才会进行++操作

img测试

而后者运算与前者相同,不同在于++前置,先自增再取值,这后者此句输出的就为arr[2]的值:300

此范例可以使用arr++表示吗?为什么?

不可以,因为arr为指针常量,为不可修改的左值。

假设有一段程序如下:

int arr2[][3]={0,1,2,3,4,5,6,7,8,};

试问

  1. 表示数组中数值5的方式有哪些?
  2. 表示arr21元素地址的方式有哪些?
  • 首先看下题目:arr为一个二维数组,三列,行数又数组自定义
  • 共9个数值,我们用sizeof来求得行数

img

  • 得出数组拥有三行三列

img内存指示图

第一题是求得5有几种表达式,那么五在第一行第三列,arr1,我们看这个表达式能转换成几个,竟然是求值,求一个二维数组的值,那就需要以下结合

  1. 一个*和一个[]结合
  2. 两个*
  3. 两个[]
  • arr21=((arr2+1)+2) //运算过程:先与最里面括号的arr+1得到arr[1],再结合取值符号得到行地址,在结合加法运算得到列地址,从而去除列地址的5

img测试图

  • arr1=**(arr2)+5 //这个是一个不规范写法,要是在程序中估计得被骂,不过按照先取地址再加减得到值的方法这样写是对的,这是先取到arr2的首地址,为arr[0],在地址上5个int字节,得到5的数据,这个表达式等于是把整个二维数组看做一位数组

img

  • arr21=*(arr2[1]+2),因为[ ]与*优先级相同,由右右左结合,这先取到arr2[1]的行地址,在加上两个int类型字节得到列地址,然后结合取值(*)符号进行取值

img

表示arr21元素地址的方式有哪些?

第二小题,这一题它要求输出地址,所以只需要拥有一个*或者[]就可以了

  • 首先我们考虑到的是取地址符号&,第一个当然是直接&arr21啦

img

  • &arr21=*(arr2+1)+2 //先计算最里面的将arr2 加上一行int类型(3个)字节,然后结合取值符取得地址,最后再加上两个int类型的地址

img

  • &arr21=arr2[1]+2 //应为*与{]的作用相同,则这题也是先取得arr[1]的行地址再加上两个int类型的字节

img

  • &arr21=*ar2+5 //又是一个不规范的写法,但是还是可以完成题目要求,取到arr2的首地址然后加上5个int字节

img

有一段程序如下:

char *str[4]={"National","Chiao","Tung","University"};

回答问题之前我们来解析题目,str为一个指针数组,里面包含四个均为char*类型的元素,str先与[]结合,为数组名

试回答以下问题:

可否利用str++将str指向下一个元素的地址?

  • 不可以,应为str为数组名,是不可修改的左值

printf("%c",*(*(str+3)+4));

  • 输出格式为单个字符,先与最里面括号集合,在str的首地址上像左偏移33=9个char类型地址指向str[3],再结合取值()取到str[3]的行地址,在结合第二个括号+4运算得到str3的地址,最后集合后面的指针取到str3的值,为e

printf("%s",*(str+3));

  • 输出格式为字符串,先与括号内的结合,在str首地址三偏移九个char类型地址指向str[3],再取到str[3]的地址,这时把该地址传给printf函数,函数会按照%s字符串格式,从该地址的第一个字符,到0结束该地址的值。
  • 至于为什么是用字符串显示去输出地址,这个问题我也考虑了很久,甚至怀疑自己的逻辑运算是错误的,但是经过测试,

img

img经过测试,两种均为正确

我所思考的问题是在str+3的时候计算机是怎么运算的呢,再str的首地址上加上九个字节得到第三行的首地址吗?

img

经过测试,并不正确,那我们就能得出结论,str为一个指针数组类型,里面的每一个元素都是char* 指针类型元素,就像一个二级指针,储存的值都为每个元素的首地址,我们来测试一下,已知str[3]为一个地址,我们取下这个地址的地址

img

到这儿我又有了一个疑惑,为什么这是一个地址但是字符串格式可以输出呢?那么字符可以那么是不是单个字符也可以呢?str[3]指向的是University的首地址,用单个字符输出是不是就得到U了呢?

img

结果显示,单个字符并不可以输出结果

再次验证了,在str+3这一步,依旧只是一个地址,我们试着取下这个地址的值

img

加了个取值符在前面,就获得了正确的结果,上边打了马赛克是为了让图片更加直观,而最后一句为什么注释,是因为虽然这段语句现在没有报错,但是在测试的时候就出现了内存冲突

img

到这儿所有问题都迎刃而解了,str为数组名,在str+3运算的时候只指向它的行地址,也就是第三行的首地址,然后用[]或*取到行地址,而字符串格式的输出条件是碰到0的时候结束输出,而这时再栈中的地址是连续性的,*str[3]为取到地址后取值,而该内存中存储的是U,因为他们同处一行,下一个地址中存储的依旧是字符n,而并非0,所以出现了异常。

而str[3]可以输出是因为从str[3]的首地址开始,读取该行包含的所以字符,到0结束(这段结论可能对各位来说没有意义,但是对我意义却颇大)也希望能帮助在指针数组卡壳的朋友彻底的理解指针数组

我当时被书上的一级指针讲解误导了,str+1,并不等于str[1],前者是在str的首地址上进行运算,也就是str[0]的地址上,后者是结合了[]取到了str+1的值进行运算

img

其他推断同理。

img附上所有注释

如何打印出NCTU这四个字?

for(int i=0;i<4;i++)
{
printf("%C",str[i][0]);
}

img

最后一题的代码进行一个运算分析:for循环就不说了,大家都会,以字符的格式逐个输出,stri的地址,我们转换下表达式更为直观,*str[i]

img

i为str指针数组内包含的元素个素,[]符号取相对应的地址,*符号取相对应的值。

下面来个代码调试题:

#include<stdio.h>
#include<conio.h>
int main()
{
    char str[][20] = { "Department","of","Information","Management" };
    //定义一个每行拥有20个元素的指针数,数组中每个元素都是一个char类型的指针
    //存放着每行的首地址
    printf("行:%d\n", (sizeof(str) / sizeof(str[0][0]))/4);
    //可通过求出数组总地址来确定行地址
    int i;//定义一个变量i,类型为int

    printf("Output address of array element:\n");//中文为:数组元素的输出地址
    for (i = 0; i < 4; i++)//定义一个for循环,初始值为0,,判定条件小于4.运算递增
    {
        printf("str+%d=%x\n", i, str + i);
        //逐个输出str内每个元素的己值
    }
    printf("\n");

    for (i = 0; i < 4; i++)
    {
        printf("*(str+%d)=%x\n", i, *(str + i));
        //先于括号里面的结合,再结合取值符号,逐个输出str的己值,数组内每个元素的首地址
    }
    printf("\n");

    for (i = 0; i < 4; i++)
    {
        printf("str[%d]=%x\n", i, str[i]);
        //[]与*作用相同,逐个输出str的已值,与上语句意义相同
    }
    printf("\n");

    printf("Output String or character:\n");

    for (i = 0; i < 4; i++)
    {
        printf("str[%d]=%s\n", i, str[i]);
        //读取str内每个元素的行地址,利用字符串格式,到\0结束输出
    }
    printf("\n");

    for (i = 0; i < 4; i++)
    {
        printf("*str[%d]=%c", i, *str[i]);
        //先结合[],取到每个元素的行地址,再结合取值符号取到每一行首地址的值
        //以字符形式输出
    }
    printf("\n");

    for (i = 0; i < 4; i++)
    {
        printf("*(*(str+%d))=%c", i, *(*(str + i)));
        //先结合括号里面,获得每个指针元素的己址,再结合取值获得己值,就是每行的首地址
        //再结合外面的指针符号取到每行的值
    }
    getchar();
    return 0;

}
  • 由于题目并无地址详细要求,我们来分析一下这道题:
  • []等同于*,所以这str是一个指针数组,数组内的每一个元素都是指针类型
  • 每一个元素包含的都是一个地址,数组内的首地址
  • 而str可理解为一个二维数组,所以取值需要[],*,[][]
  • 取地址需要:*,[ ]

然后我们来看看这段代码所用到的表达式

  • str+i //啥都没,输出的就是每个指针类型的地址
  • *(str+i)//有一个*取到每个指针内的值,还是为地址,每个元素的行地址,可为字符串
  • str[i] //一个[],取到每个元素的行地址,可为字符串
  • *str[i] //一个*一个[],取到每个元素首地址的值,为字符
  • *(*(str+i))//两个*,取代每个元素首地址的值,为字符

这道题重点在与对取值和取地址还有表达式转换的理解

和[]与*的关系,我们最后看看运行结果

img

然后来看看第二段代码

#include<stdio.h>
#include<conio.h>
    int main()
    {
        char str[][20] = { "Department","of","Information","Management" };
        //定义一个二维数组,可以看做指针数组,指向每行拥有二十个元素的数组
        printf("行数=%d\n", (sizeof(str) / sizeof(str[0][0])) / 20);
        //可以通过总数组长度来求出行长度
        printf("\n");

        printf("*str[0]+3=%c\n", *str[0] + 3);
        //先取到str+0的已值,为0行的首地址,再取到首地址内包含的元素“D”,在元素D的二进制上+3,输出为G
        printf("\n");

        printf("*(str[0]+3) = % c\n", * (str[0] + 3));
        //先取到str+0的已值,为0行的首地址,再在地址上+3个char字节长度,再取值,输出为a
        printf("\n");

        printf("*(*(str+2)+2=%c\n", *(*(str + 2) + 2));
        //先取到str+2的己值,在str[2]的首地址上加上3个char字节长度,再结合最外面的*取值得到f
        printf("\n");

        printf("*(*(str+3)+4)=%c\n", *(*(str + 3) + 4));
        //先取到str+3的已值,然后在str[3]的首地址上加上4个char字节长度,再结合最外面的*取值得到g

        getchar();//从缓冲区读取所有数据后输出
        return 0;//返回为0
    }

这段代码中需要注意一个表达式:

*str[0]+3:str是一个二维数组,所以看到一个*和一个[]就明白这是取值,然后运算符优先级,先与[]结合得到str+0的已值,也就是str[0]的首地址,再取到地址内包含的元素D,最后执行+3

执行+3的时候,计算机在运算时都是以二进制进行操作,+3当然也是在D的二进制上进行+3,然后对应ASCII码以字符格式输出

img

输出为G.

最后我们来看看运行结果:

img

下一题:

来放松放松做到简单点的题

有一段程序如下:

char* str[]={"Object","Oriented","Programming","Language"}

试回答下列问题:

  • *(str+3)以%s打印出
  • **(str+3)以%c打印出
  • *(*(str+3)+3)以%c打印出
  • str+1)以%c打印出
  • **(str+2)+5以%c打印出

做题前先来分析题目语句,定义一个char*类型的一维指针数组,里面每一个元素都为char*类型,包含着一个地址

再来看第一道题:*(str+3)以%s打印出:先与括号内的结合指向str[3],然后结合取值符号得到str[3]的首地址:Language的首地址==L的地址,以字符串格式打印出结果应为:Language

第二道题:**(str+3)以%c打印出:先与括号内的结合指向str[3],然后结合取值符号得到str[3]的首地址,Language的首地址==L的地址,再结合取值符号取到L,以字符格式打印出结果应为L

第三道题:*(*(str+3)+3)以%c打印出:先与括号内的结合指向str[3],然后结合取值符号得到数据Language的首地址,在首地址上加三个char*字节长度得到g的地址,再结合取值符号取到g,以字符格式打印出结果应为g

第四题:str+1)以%c打印出:先与括号内的结合,取到str的值为str[0]的首地址,再加上一个char*字节长度得到Object中b的地址,再结合取值符号取到b,以字符格式打印出应为b

第五题:**(str+2)+5以%c打印出:先与括号内结合,str+2指向str[2],再结合取值符号取到str[2]的首地址,再结合取值符号得到数据Programming的p,再转换成二进制进行+5运算,以ASCII码输出应为U

img思路正确

然后我们进入下一题

也来个简单的:

有以下语句:

int j[] = { 100,200,300,400,500 };
int* pa[] = { j,j + 1,j + 2,j + 3,j + 4 };
int** p2 = pa;

请回答下列问题(具有连续性的)

printf("%dn",*(*(p2+1)+1) );

printf("%dn",((pa+2)+2) );

printf("%dn",*(pa[3]+1) );

printf("%dn",**p2++ );

printf("%dn",**p2 );

做题前我们先来分析题目:

  • j为一个一维数组,长度由计算机分配,取决于元素个数
  • pa为一个一维指针数组,数组内每一个元素为int*类型,包含着一个int变量地址
  • p2为一个二级指针,指向pa的首地址

第一题:printf("%dn",*(*(p2+1)+1) );

先结合括号内的p2+1,p2所指向的pa地址上加上一个int*字节长度,此时p2指向了pa[1],再结合取值符,取到pa[1]的值为j+1,指向j[1],再结合第二个括号的加一运算,在j[1]的地址上加上一个int*类型,此时pa指向j[2],再取值输出值应为300

第二题:printf("%dn",*(*(pa+2)+2) );

先结合括号内的pa+2,再取到pa+2的值为j+2,这时pa指向j[2],再j[2]的地址上加上两个int*字节长度,得到j[4]的地址,再取到j[4]的值为500

第三题:printf("%dn",*(pa[3]+1) );

先结合括号内的pa[3]+1,取到pa+3的值为j+3,再j[3]的地址上加一个int*字节长度,得到j[4]的地址,再结合取值符号,取到j[4]的值为500

第四题:printf("%dn",**p2++ );

由运算符优先级,由右向左运算,后置自增,取到p2的值为pa[0]==j的地址,再取到j的值为100

printf("%dn",**p2 );

上一题后置自增,在这一题p2先自增一个int*字节长度,指向pa[1],再结合取值得到pa[1]的值j[1]的地址,再结合取值得到j[1]的值为200

最后附上运行结果:

img

进入下一题:

#include<stdio.h>
#include<conio.h>
int main()
{
    int i[5] = { 10,20,30,40,50 };
    //定义一个一维数组,为int类型,数组存有五个int类型的数据
    int* ptr[] = { i,i + 1,i + 2,i + 3,i + 4 };
    //定义一个一维指针数组,为int*类型,数组内每个元素都为int*元素
    int** p2 = ptr;
    //定义一个二级指针,指向ptr的首地址

    int k;

    for (k = 0; k < 5; k++)//定义一个for循环,从0开始,判定条件小于5,运算为递增
    {
        printf("i+%d=%x\n", k, i + k);
        //逐个输出i数组内的数据
    }
    printf("\n");

    for (k = 0; k < 5; k++)
    {
        printf("ptr[%d]=%x\n", k, ptr[k]);
        //逐个输出ptr指针数组内的数据
    }
    printf("\n");

    for (k = 0; k < 5; k++)
    {
        printf("ptr+%d=%x\n", k, ptr + k);
        //逐个输出ptr指针数组内每个元素的地址
    }
    printf("\n");

    printf("ptr=%x,p2=%x\n\n", ptr, ptr + 2);
    //输出ptr的首地址,p2的首地址

    p2++;//执行p2自增
    printf("After p2++\n");
    printf("p2=%x,ptr=%x,p2-ptr=%d,**p2=%d\n\n", p2, ptr, p2 - ptr, **p2);

    *p2++;
    printf("After *p2++");
    printf("p2=%x,ptr=%x,p2-ptr=%d,**p2=%d\n\n", p2, ptr, p2 - ptr, **p2);

    *++p2;
    printf("After *++p2");
    printf("p2=%x,ptr=%x,p2-ptr=%d,**p2=%d\n\n", p2, ptr, p2 - ptr, **p2);

    ++* p2;
    printf("After ++*p2");
    printf("p2=%x,ptr=%x,p2-ptr=%d,**p2=%d\n\n", p2, ptr, p2 - ptr, **p2);


    system("pause");
    return 0;
}

这题重点在,p2执行运算后各表达式输出的值

第一段输出语句:

p2++;//执行p2自增
printf("After p2++n");
printf("p2=%x,ptr=%x,p2-ptr=%d,*p2=%dnn", p2, ptr, p2 - ptr, *p2);

剖析:p2指向的是ptr的首地址,也就是ptr[0]的首地址,当p2++后,p2此时指向ptr[1]的地址,操作与p2上,影响的只是p2的指向

  • p2:输出的应为ptr[1]的地址,以十六进制输出,等价于输出ptr+1
  • ptr:输出的应为ptr[0]的地址,以十六进制输出,等价于输出ptr+0
  • p2-ptr:输出的应为1,p2自增了一个int*字节长度
  • **p2:输出应为20,结合第一个取值取到p[1]的值i,为i[0]的地址,结合第二个取值取到i[0]的值为20

注意点:两个指针相减时候,得到的结果是 两个地址在内存中间隔多少个指针类型的字节倍数,右偏移量为正,左偏移量为负

运行结果:

img

第二段语句输出:

*p2++;
printf("After *p2++");
printf("p2=%x,ptr=%x,p2-ptr=%d,*p2=%dnn", p2, ptr, p2 - ptr, *p2);

剖析:在上一题p2已经执行一次自增指向了ptr[1],由右向左执行,先结合自增在p2上自增一个int*字节长度,再取到p2的值为ptr[2]也就是i+2,为i[2]的地址,自增运算还是在p2上,取值符号并不影响结果,此时p2指向ptr[2]

  • p2:输出的应为ptr[2]的地址,以十六进制输出,等价于ptr+2
  • ptr:输出的应为ptr[0]的地址,以十六进制输出,等价于ptr+0
  • p2-ptr:输出的应为2,,此时p2执行两次自增,向右偏移两个int*字节
  • **p2:输出的应为30,此时p2指向ptr[2]指向i[2],第一个取值取到ptr[2]的值为i+2,第二个取值取到i[2]的值为30

运行结果:

img

第三段语句输出:

*++p2;
printf("After *++p2");
printf("p2=%x,ptr=%x,p2-ptr=%d,p2=%dnn", p2, ptr, p2 - ptr, p2);

剖析:在上一语句,p2已经指向了ptr[2],有右向左执行,先自增,p2自增一个int*字节,向右偏移一位,此时指向ptr[3],再取值,得到ptr[3]的值为i+3,取值符号不影响输出结果

  • p2:输出的应为ptr[3]的地址,以十六进制输出,等价于ptr+3
  • ptr:输出的应为ptr[0]的地址,以十六进制输出,等价于ptr+0
  • p2-ptr:输出的应为3,p2进行了三次自增指向ptr[3],在ptr的首地址上向右偏移了3个int*字节
  • **p2:输出的应为40,此时p2指向ptr[3],指向i[3],第一个*取到ptr[3]的值为i+3,第二个*取到i[3]的值为40

运行结果:

img

第四段语句:

++* p2;
printf("After ++p2");

printf("p2=%x,ptr=%x,p2-ptr=%d,p2=%dnn", p2, ptr, p2 - ptr, *p2);

剖析:此时p2已经执行了三次自增指向了ptr[3],由运算符优先级,先与*结合得到p2的值i+3,自增一个int*字节,得到此时ptr[3]指向i[4]

  • p2:输出的应为ptr[3]的地址,以十六进制输出,等价于ptr+3
  • ptr:输出的应为ptr[0]的地址,以十六进制输出,等价于ptr+0
  • p2-ptr:输出的应为3,输出的应为3,p2进行了三次自增指向ptr[3],在ptr的首地址上向右偏移了3个int*字节
  • **p2:输出的应为50,此时p[3]指向的为i[4],题目中自增运算符操作在p[3]原先的值i+3上

运行结果:

img

进入下一题:

#include<stdio.h>
#include<conio.h>
    int main()
    {
        char str[][10] = { "computer","printer","monitor" };
        //定义一个二维数组,每行有是个元素
        printf("行地址:%d", (sizeof(str) / sizeof(str[0][0])) / 10);
        char *pstr[]= { "computer","printer","monitor" };
        //定义一个指针数组,长度由计算机自分配
        printf("str[0]=%s\n", str[0]);
        //先取到str+0的已值,再以字符串格式输出,输出结果应为computer,到\0结束
        printf("*pstr=%s\n", *pstr);
        //取到pstr的已值进行字符串格式输出,等价与pstr[0],输出结果应为computer
        printf("str[1]=%s\n", str[1]);
        //取到str+1的已值,再以字符串格式输出,输出结果应为printer,到\0结束
        printf("*(pstr+1)=%s\n", *(pstr + 1));
        //取到pstr+1的已值进行字符串格式输出,输出结果应为printer,到\0结束
        printf("str[2]=%s\n", str[2]);
        //取到str+2的已值进行字符串格式输出,输出结果为monitor,到\0结束
        printf("*(pstr+2)=%s\n", *(pstr + 2));
        //取到pstr+2的已值进行字符串格式输出,输出结果应为monitor,到\0结束
        getchar();
        return 0;
    }

上面把表达式的问题说的很详细了,这一题考的无非是指针数组与二维数组之间的联系罢了,我们来分析一下两个语句

char str[][10] = { "computer","printer","monitor" };
char *pstr[]= { "computer","printer","monitor" };

第一个为二维数组,[]可以看做,则可以转换成指针数组 str[10],每个指针内包含着十个字节长度的数组,指向该数组的首地址

img

而第二个就是为一个指针数组,拥有三个char类型的指针,每个指针内包含着一数组,数组长度由计算机自主a分配,每个指针指向所包含数组的首地址

他们两者看起来好像并无什么区别,但是从内存分上并能明显的看出区别,我会开设另一篇笔记去说明两者

而[]在作用上与*相同,但是意义不同,除了一个在堆一个在栈,还有一个很重要的就是:

  • 指针数组是一个变量,可操作自增,自减,例如:

img

  • 而二维数组直接操作的是数据,不能进行自增自减

img

这儿为了解除疑惑,再说str与(*str)的区别

img

他们数值相同,但是意义不同:str[0]为行地址,&str0为0行0列地址,*str为数组行地址,str为数组首地址,&str为数组的首地址

在给指针赋值时,最好使用&,取地址,否则代码极为不安全

终于结束了这一题,虽然我很不愿意结束,但是在这一篇幅内去讲解内存分布实在时间不够,我会开设另以笔记去探讨指针,指针数组,数组指针三者的区别以及使用。

下一题:

#include<stdio.h>
#include<conio.h>
int main()
{
    char* str[] = { "Taiwan","Porland","philipine","Hong Kong","England","Sweden" };
    //定义一个一维指针数组,每个元素都为char*类型
    char** pp[] = { str,str + 1,str + 2,str + 3,str + 4,str + 5 };
    //定义一个二维指针数组,里面每一个元素都为一维指针的地址
    char*** ppp = pp;
    //定义一个三维指针指向pp的首地址

    int i;
    for (i = 0; i < 6; i++)//判断条件为i<6,运算为i++
    {
        printf("str[%d]=%s\n", i, str[i]);
        //逐个取str的地址,以字符串格式输出值
    }
    printf("\n");
    printf("%c", *(**ppp + 2));

    printf("%c", *(**ppp + 1));

    printf("%c", *(*(*ppp + 2) + 1));
    
    printf("%c", *(*ppp[3] + 6));
    printf("%c", ppp[4][0][5]);
    printf("%c", *(ppp[5][0] + 2));
    printf("\n");
    getchar();
    system("pause");
    return 0;
}

在做分析之前先看下指向流程图:

img

为啥没注释,因为这道题要一个语句一个语句来分析,我们先来分析第一个:

printf("%c",(*ppp+2));

  • 从图中可以看出,现在ppp上操作取值,取到pp[0](地址)
  • 再取到pp[0]的值,为str[0](地址)可理解为*(str+0)
  • 再str[0]的地址上进行运算,加上两个char字节
  • 再取到运算完后的结果,得到字符i

这题需要注意的点有:

  • 在进行加法运算的时候,此时实在str的首地址是做操作
  • 而不是在pp的地址上进行操作,从图中可以看出
  • 由于有两个*符号,取到pp的地址后,pp的地址中存储的是str的首地址
  • 所以进行运算是在str的首地址上进行运算,看一包裹图

img

  • 通俗点说,这儿的运算是在两个*的时候进行的,就是在粉色框框里进行的,从0开始,加2

可能我这样说会易懂一点,自己的理解,非常诚恳的希望大佬能指出错误,在写下这篇笔记的时候,我的思路还是很模糊,后期可能会做修改

有了上面语句的分析案例,我们继续下面的语句

printf("%c", *(**ppp + 1));

  • 由运算符优先级,先计算括号内的,取到ppp的值,为pp的地址
  • 再作用于pp的地址,取到地址内包含的值,为str的地址
  • 在str的首地址上,进行加一运算,加上一个char类型的字符长度
  • 再结合最外部指针进行取值,得到字符a

printf("%c", **(*(*ppp + 2) + 1));

  • 由运算符优先级,先计算最里面括号的
  • 取到ppp的值,为pp的首地址,进行加2
  • 在pp的首地址上加上两个字节,操作与pp得到str+2
  • 取到str+2的值,为字符串philipine的首地址
  • 进行加一操作,得到h的地址,取值,得到h

printf("%c", (**ppp[3] + 6));

  • 由运算符优先级,先计算最里面括号的
  • ppp先与[]结合,取到ppp[3]的值为pp[3]
  • 再取到pp[3]的值为str+3为Hong Kong的地址
  • 在str[3]的地址上加上6个char类型的字节长度
  • 再取到运完后的str3的值,为o(空格占一字节)

printf("%c", ppp4[5]);

  • 先转换表达式,更为直观==*(ppp+4)+5)
  • 先于括号里的结合,在ppp的地址上加4个char字节长度
  • 再取到该地址里边的值,为pp[4]的地址
  • 由于是pp4则直接取到pp[4]的首地址的值为str[4]的地址(str+4)
  • 在str+4的首地址上加上5个char字节长度,得到字符n

printf("%c", *(ppp5 + 2));

  • 先转换表达式,更为直观==*(ppp+5)+2)
  • 先计算括号里的,取到ppp+5的值为pp[5]的地址
  • 再取到pp[5]的值为str+5的地址
  • 在str+5的地址上进行加上两个字节长度
  • 最后取到该地址存储的值,为e

最后附上运行结果图:

img

我们接下来以上面那道题做一个扩展,开始下面这道题

#include<stdio.h>
#include<conio.h>
int main()
{
    char* str[] = { "Taiwan","Porland","philipine","Hong Kong","England","Sweden" };
    //定义一个一维指针数组,每个元素都为char*类型
    char** pp[] = { str,str + 1,str + 2,str + 3,str + 4,str + 5 };
    //定义一个二维指针数组,里面每一个元素都为一维指针的地址
    char*** ppp = pp;
    //定义一个三维指针指向pp的首地址

    int i;
    for (i = 0; i < 6; i++)
    {
        printf("**(ppp+%d)=%s\n", i, **(ppp + i));
    }
    printf("\n");
    
    printf("After **++ppp...\n");
    printf("%s\n\n", **++ppp);
    
    printf("After *--*++ppp...\n");
    printf("%s\n\n", *-- * ++ppp);
    
    printf("After *--*++ppp+2...\n");
    printf("%s\n\n", *-- * ++ppp + 2);

    printf("After ++**ppp...\n");
    printf("%s\n\n", ++ * *ppp);

    printf("After ++**++ppp...\n");
    printf("%s\n\n", ++ * *++ppp);

    system("pause");
    //1101 1101 0111 1011 0110 1010
    return 0;
}

为啥没注释,因为这道题也要每个语句来剖析,这题比较上题不过是换了表达式罢了,操作于在ppp上边

我们先来看看ppp是什么

  • PPP为指针变量名,他自身为一个三级指针
  • 指针指向PP的地址,通过指针来操作PP所指向的数组
  • ppp的值等于pp的首地址,在ppp上操作等于就是在pp的地址上操作

接下来我们看题目,这题有个很隐晦的坑,我当时就踩了进去!!!代码具有连续性!!!

**++ppp

  • 由运算符优先级,前置自增,由右向左结合
  • 先在ppp上自增,加上一个char*类型字节
  • 再结合第一个*取值得到pp[1],为str+1,为str[1]的地址
  • 再取到str+1得到str[1]数据:Porland的首地址
  • 也就是P的地址
  • 以字符串格式输出,到0结束,输出应为Porland

*--*++ppp

  • 由于上题在ppp上已经自增指向pp[1]了
  • 由运算符优先级,前置自增,由右向左结合
  • 先在ppp上自增,加上一个char*字节,得到pp[2]的地址
  • 再取值得到str+2,再前置自减,在str[2]的地址上减去一个char*字节长度
  • 得到str+1的地址,再取值,得到str[1]数据Porland的首地址
  • 也就是P的地址
  • 以字符串格式输出,到0结束,输出应为Porland

*--*++ppp+2

  • 由于在上题ppp已经实现了两个自增操作指向了pp[2]
  • 由运算符优先级,前置自增,得到pp[3]的地址
  • 再取值得到pp[3]的值str+3,在str[3]的地址上实现前置自减
  • 减去一个char*字节长度,得到str[2]的地址
  • 再取值得到str[2]数据philipine的首地址
  • 也就是p的地址
  • 再执行+2,加上两个char*字节长度,得到数据i的地址
  • 输出应为ilipine

++**ppp

  • 由于上一题ppp执行了三次自增,已经指向了pp[3]
  • 而pp[3]所指向的str[3]在上题已经执行了自减
  • 所以这时pp[3]指向了str[2]
  • 由于运算符优先级,先取到ppp所指向pp[3]的值str[2]的地址
  • 再取到str[2]的值为数据Philipine的首地址
  • 再首地址上进行自增操作得到h的地址
  • 再以字符串格式输出,输出应为hilipine

++**++ppp

  • 在这题ppp执行了三次自增,指向pp[3]
  • 而pp[3]执行了一次自减,指向str[2]
  • pp[2]指向一次自减,指向了str[1]
  • 由运算符优先级,前置自增,在ppp上加上一个char*字节长度
  • 这时ppp指向了pp[4]
  • 再结合取值操作符得到pp[4]的值为str+4,这时pp[4]指向str[4]
  • 再结合第二个取值操作符得到str[4]数据:England的首地址
  • 为E的地址
  • 再结合前置自增,在E的地址上增加一个char*字节长度
  • 得到n的地址,以字符串格式输出
  • 输出应为:ngland

我们来看看运行结果:

img

这道题我们需要注意一个很重要的坑:

  • 二维指针数组内存放的是一维指针数组的地址
  • 自增具有连续性,前置该语句自增,后置语句执行完后自增
  • 在这类题型的时候千万注意,指针类型数据,指针会改变指针指向
  • ppp所指向的是pp的首地址,ppp为一个三级指针变量
  • ppp的值为pp的地址,不要把指针与指针数组混淆
  • 在ppp进行操作的时候,操作的就是pp的地址
  • pp所指向的是str的地址,为一个二维指针,内存放的是一维指针的地址

img

  • 注意每段语句的运算对指针的指向影响。这题有个很隐晦的坑,我当时就踩了进去!!!代码具有连续性!!!

进入下一题:

有以下语句:

char* s[] = { "National","Chiao","Tung","University" };
char** sa[] = { s + 3,s + 2,s + 1,s };
char*** p3 = sa;

试着说明一下输出意义

  • printf("%sn", **p3);
  • printf("%cn", (*(p3 + 1) + 2));
  • printf("%cn", ((sa[2] + 2) + 1));

在做题之前先来剖析下题目:

  • S为一个指针数组,s内的每一个元素都为一个char类型的指针,每个指针内都包含着一个地址
  • sa为一个二维指针数组,sa内每一个元素都为一个char*类型的指针,每一个指针内都包含着一个一维指针数组的地址
  • p3为一个三级指针,指向sa的首地址,也就是sa[0]的地址

第一语句:printf("%sn", **p3);

p3指向sa[0],先与第一个结合取到sa[0]的值为s+3,也就是s[3]的地址,再结合第二个得到University元素的首地址,也就是U的地址,再以字符串格式输出,到0结束,输出结果应为University

第二语句:printf("%cn", (*(p3 + 1) + 2));

先与括号内的结合在p3上加上一个int*字节长度,然后取值得到p3[1],也就是s+2,再结合第二个取值得到s[2]的值,也就是数据:Tung的首地址,再加上2得到n的地址,再结合最外面的取值得到n,以字符格式输出结果为n

第三语句:printf("%cn", ((sa[2] + 2) + 1));

先与括号内的结合得到s+1,也就是s[1]的地址,再s[1]的地址上加上两个int字节长度得到s[3]的地址,再结合第一个符号取值得到:University的首地址,在U的地址上加上一个int字节长度得到n的地址,最后结合第二个取值得到n,以字符格式输出结果为n

运行结果:

img

写完了!!!!!!到这里指针与数组的所有题目就写完了!!!这段话等我清醒点再编辑!!!!!!还有一个程序实战题和命令行自变量题会另开笔记!!!

本文链接:

https://nullcode.fun/106.html
1 + 9 =
快来做第一个评论的人吧~