[题集]指针与数组
陈述下列语句的意义
(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
经测试,预测结果无错
- n //输出应为数组元素的首地址,也就是10的地址,由题得1010
- *n //输出应为数组元素首地址的值,也就是1010
由于n是一个二维数组,需以两个*或者两个[ ] 或者一个*搭配一个[ ]才能的到元素的值
- **n //二级指针可理解为取一级指针的值,由于未进行其他定义,则输出结果也应为10
经过测试,预测结果无错
- n[2] //输出第三行的首地址,也就是n2的地址
经过测试,预测结果无错
- n2 //输出n2,也就是第二行第一列的值,输出应为80
- *n[2]+1 //由于*运算符大于+运算符,*与[]优先级相同,顺序由右向左运算,则n先与[2]结合,再取值,然后+1,则是地址加一,输出的值应为第二行第一列50的地址,应题目要求为10112
- *(n+1)//括号优先级最大,n先与+结合再取值,则输出应为n第一行的首地址,也就是40的地址
测试结果,与预算无误
由于n是一个二维数组,需以两个*或者两个[ ] 或者一个*搭配一个[ ]才能的到元素的值
- **(n+2) //上述做了那么多的强调,这儿看到二维数组的两个指针就应该很明白了嘛!就是n2,输出的为70,不信?我们来测试一下!
由于n是一个二维数组,需以两个*或者两个[ ] 或者一个*搭配一个[ ]才能的到元素的值
- n[2]+2 //[ ]运算符比+运算符要高,所以先指向数组第二行+2就是地址前进2*2个字节,输出结果应为n2的地址,还是那句话
由于n是一个二维数组,需以两个*或者两个[ ] 或者一个*搭配一个[ ]才能的到元素的值
测试一下:
- *(n[1]+2) //重复那么多次,一眼看出是*与[ ]结合,输出的应为值,括号优先级最大,先执行n[1]+2=n1的地址,再取值,应为60
- *(*n+2) //一样的,两个*运算符,括号优先级最大,先看括号里的,n先与*结合,取到n[0]的行地址,再+2取到n0的地址,再与括号外的*结合取值,应为30,题目最后再测试测试吧!
经过测试,预算结果没错,思路基本正确
这道题我觉得是对掌握数组指针运算的最好的题目,最好记住 !
由于n是一个二维数组,需以两个*或者两个[ ] 或者一个*搭配一个[ ]才能的到元素的值
假设存在如下声明:
- char *c[]={"enter","new","point","first"};
- char **cp[]={c+3,c+2,c+1,c};
- char *cpp=cp;
在做题的时候,我们先来分析题目的的意思
可能解释比较模糊,不定时修改
然后看看题目要求:
- 试问下列各项输出结果是什么(以%s输出,每一小题均存在连贯性)
- **++cpp
- --++cpp+3
- *cpp[-2]+3
- cpp-1+1
第一题:“**++cpp”运算分析:自增与取值运算符优先级相等,运算顺序由右向左
- cpp指向cp的首地址,先与++结合,在cp首地址上加一个char*长度,指向cp[1]
- 再结合第一个指针取到cp[1]的值,为c+2
- 再结合第二个指针取到c+2的值,为c[2],最后输出结果为point
测试结果
第二题:--++cpp+3,的运算分析,自增自减运算符和指针运算符优先级相等由右向左,最后考虑加法运算
- cpp在上题指向了cp[1],这题先与自增结合指向cp[2]
- 再结合第一个指针运算,取值cp[2]得到c+1,也就是为c[1]
- 再结合自减运算符得到c[0],再结合指针运算取值得到enter
- 最后运算加法,从第三列开始输出到0结束,输出结果为er
测试结果
第三题:重头戏,*cpp[-2]+3,数组下标不允许有负数出现,咱们转换成看得懂的表达式:*(*(cpp-2))+3
- 括号优先级最大,cpp在上一题指向了cp[2],可以转换成cp+2,在这一题先与最里面的cpp-2结合,这时它在cpp指向的地址上减2,这时他指向了cp[0]
- 再结合中间括号的指针运算符,取到c+3
- 再结合最外面的指针运算符,取到c[3]的值为first
- 最后结合加法运算,在字符首地址加上3字节,到r开始,输入结果为st
测试结果
第四题:cpp-1+1更加重头戏,一样,转换成看得懂的表达式*(*(cpp-1)-1)+1
- 还是一样,在cpp指向的cp[2]中进行了减一操作,这时指向了cp[1]
- 再结合第一个指针运算符,取了cp[1]的值也就是c[2]
- 再与外括号的-1结合,在c[2]的地址上减了一,这时它指向了c[1]
- 再结合第二个指针运算符,取到了c[1]的值也就是new
- 最后结合加法运算符,表示将new这个字符串从下标1开始,作为首地址传给了printf,默认是0开始,n被跳过,输出为er
测试结果
题目总结:这一题加深了我对[]和*的理解,我卡在了cpp++与cpp+1的这个问题绕不过来,我一直把第三题和第四题连贯起来,也不懂第三题为什么会输出st,其实在cpp的地址上进行运算的只有第一题和第二题的前置自增运算符,++cpp是在cpp的首地址上跨越一个内存,而cpp[-2]是取了cpp指向cp的值上进行操作,一个是直接操作cpp的指向,而另一个是在它指向的值上进行操作,虽然操作的都是cp的地址,但是前者会影响cpp的指向,而后者却不会影响cpp的指向。
有一段程序如下:
- int arr[]={100,200,300,400,500};
- int *ptr=arr+1;
并假设arr数组的起始地址为22ff50,而ptr的内存地址为22ff4c,试回答下列题目,并注明是地址还是值。
arr[2]= arr为数组名,2是数组下标,语义为取arr[2]的值,输出结果为300,输的的是值
测试结果,思路正确
*(arr+2)=先与括号内的结合,在arr的首地址上加2获得arr[2]的地址,再取arr[2]的值,输出的为值,300
经测试,结果一样
arr+2=在arr的首地址上加2个int字节,输出的为地址,由题意首地址为22ff50,加4*2=8得到22ff58
思路正确
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,在下一语句才会进行++操作
测试
而后者运算与前者相同,不同在于++前置,先自增再取值,这后者此句输出的就为arr[2]的值:300
此范例可以使用arr++表示吗?为什么?
不可以,因为arr为指针常量,为不可修改的左值。
假设有一段程序如下:
int arr2[][3]={0,1,2,3,4,5,6,7,8,};
试问
- 表示数组中数值5的方式有哪些?
- 表示arr21元素地址的方式有哪些?
- 首先看下题目:arr为一个二维数组,三列,行数又数组自定义
- 共9个数值,我们用sizeof来求得行数
- 得出数组拥有三行三列
内存指示图
第一题是求得5有几种表达式,那么五在第一行第三列,arr1,我们看这个表达式能转换成几个,竟然是求值,求一个二维数组的值,那就需要以下结合
- 一个*和一个[]结合
- 两个*
- 两个[]
- arr21=((arr2+1)+2) //运算过程:先与最里面括号的arr+1得到arr[1],再结合取值符号得到行地址,在结合加法运算得到列地址,从而去除列地址的5
测试图
- arr1=**(arr2)+5 //这个是一个不规范写法,要是在程序中估计得被骂,不过按照先取地址再加减得到值的方法这样写是对的,这是先取到arr2的首地址,为arr[0],在地址上5个int字节,得到5的数据,这个表达式等于是把整个二维数组看做一位数组
- arr21=*(arr2[1]+2),因为[ ]与*优先级相同,由右右左结合,这先取到arr2[1]的行地址,在加上两个int类型字节得到列地址,然后结合取值(*)符号进行取值
表示arr21元素地址的方式有哪些?
第二小题,这一题它要求输出地址,所以只需要拥有一个*或者[]就可以了
- 首先我们考虑到的是取地址符号&,第一个当然是直接&arr21啦
- &arr21=*(arr2+1)+2 //先计算最里面的将arr2 加上一行int类型(3个)字节,然后结合取值符取得地址,最后再加上两个int类型的地址
- &arr21=arr2[1]+2 //应为*与{]的作用相同,则这题也是先取得arr[1]的行地址再加上两个int类型的字节
- &arr21=*ar2+5 //又是一个不规范的写法,但是还是可以完成题目要求,取到arr2的首地址然后加上5个int字节
有一段程序如下:
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结束该地址的值。
- 至于为什么是用字符串显示去输出地址,这个问题我也考虑了很久,甚至怀疑自己的逻辑运算是错误的,但是经过测试,
经过测试,两种均为正确
我所思考的问题是在str+3的时候计算机是怎么运算的呢,再str的首地址上加上九个字节得到第三行的首地址吗?
经过测试,并不正确,那我们就能得出结论,str为一个指针数组类型,里面的每一个元素都是char* 指针类型元素,就像一个二级指针,储存的值都为每个元素的首地址,我们来测试一下,已知str[3]为一个地址,我们取下这个地址的地址
到这儿我又有了一个疑惑,为什么这是一个地址但是字符串格式可以输出呢?那么字符可以那么是不是单个字符也可以呢?str[3]指向的是University的首地址,用单个字符输出是不是就得到U了呢?
结果显示,单个字符并不可以输出结果
再次验证了,在str+3这一步,依旧只是一个地址,我们试着取下这个地址的值
加了个取值符在前面,就获得了正确的结果,上边打了马赛克是为了让图片更加直观,而最后一句为什么注释,是因为虽然这段语句现在没有报错,但是在测试的时候就出现了内存冲突
到这儿所有问题都迎刃而解了,str为数组名,在str+3运算的时候只指向它的行地址,也就是第三行的首地址,然后用[]或*取到行地址,而字符串格式的输出条件是碰到0的时候结束输出,而这时再栈中的地址是连续性的,*str[3]为取到地址后取值,而该内存中存储的是U,因为他们同处一行,下一个地址中存储的依旧是字符n,而并非0,所以出现了异常。
而str[3]可以输出是因为从str[3]的首地址开始,读取该行包含的所以字符,到0结束(这段结论可能对各位来说没有意义,但是对我意义却颇大)也希望能帮助在指针数组卡壳的朋友彻底的理解指针数组
我当时被书上的一级指针讲解误导了,str+1,并不等于str[1],前者是在str的首地址上进行运算,也就是str[0]的地址上,后者是结合了[]取到了str+1的值进行运算
其他推断同理。
附上所有注释
如何打印出NCTU这四个字?
for(int i=0;i<4;i++)
{
printf("%C",str[i][0]);
}
最后一题的代码进行一个运算分析:for循环就不说了,大家都会,以字符的格式逐个输出,stri的地址,我们转换下表达式更为直观,*str[i]
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))//两个*,取代每个元素首地址的值,为字符
这道题重点在与对取值和取地址还有表达式转换的理解
和[]与*的关系,我们最后看看运行结果
然后来看看第二段代码
#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码以字符格式输出
输出为G.
最后我们来看看运行结果:
下一题:
来放松放松做到简单点的题
有一段程序如下:
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
思路正确
然后我们进入下一题
也来个简单的:
有以下语句:
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
最后附上运行结果:
进入下一题:
#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
注意点:两个指针相减时候,得到的结果是 两个地址在内存中间隔多少个指针类型的字节倍数,右偏移量为正,左偏移量为负
运行结果:
第二段语句输出:
*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
运行结果:
第三段语句输出:
*++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
运行结果:
第四段语句:
++* 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上
运行结果:
进入下一题:
#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],每个指针内包含着十个字节长度的数组,指向该数组的首地址
而第二个就是为一个指针数组,拥有三个char类型的指针,每个指针内包含着一数组,数组长度由计算机自主a分配,每个指针指向所包含数组的首地址
他们两者看起来好像并无什么区别,但是从内存分上并能明显的看出区别,我会开设另一篇笔记去说明两者
而[]在作用上与*相同,但是意义不同,除了一个在堆一个在栈,还有一个很重要的就是:
- 指针数组是一个变量,可操作自增,自减,例如:
- 而二维数组直接操作的是数据,不能进行自增自减
这儿为了解除疑惑,再说str与(*str)的区别
他们数值相同,但是意义不同: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;
}
在做分析之前先看下指向流程图:
为啥没注释,因为这道题要一个语句一个语句来分析,我们先来分析第一个:
printf("%c",(*ppp+2));
- 从图中可以看出,现在ppp上操作取值,取到pp[0](地址)
- 再取到pp[0]的值,为str[0](地址)可理解为*(str+0)
- 再str[0]的地址上进行运算,加上两个char字节
- 再取到运算完后的结果,得到字符i
这题需要注意的点有:
- 在进行加法运算的时候,此时实在str的首地址是做操作
- 而不是在pp的地址上进行操作,从图中可以看出
- 由于有两个*符号,取到pp的地址后,pp的地址中存储的是str的首地址
- 所以进行运算是在str的首地址上进行运算,看一包裹图
- 通俗点说,这儿的运算是在两个*的时候进行的,就是在粉色框框里进行的,从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
最后附上运行结果图:
我们接下来以上面那道题做一个扩展,开始下面这道题
#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
我们来看看运行结果:
这道题我们需要注意一个很重要的坑:
- 二维指针数组内存放的是一维指针数组的地址
- 自增具有连续性,前置该语句自增,后置语句执行完后自增
- 在这类题型的时候千万注意,指针类型数据,指针会改变指针指向
- ppp所指向的是pp的首地址,ppp为一个三级指针变量
- ppp的值为pp的地址,不要把指针与指针数组混淆
- 在ppp进行操作的时候,操作的就是pp的地址
- pp所指向的是str的地址,为一个二维指针,内存放的是一维指针的地址
- 注意每段语句的运算对指针的指向影响。这题有个很隐晦的坑,我当时就踩了进去!!!代码具有连续性!!!
进入下一题:
有以下语句:
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
运行结果:
写完了!!!!!!到这里指针与数组的所有题目就写完了!!!这段话等我清醒点再编辑!!!!!!还有一个程序实战题和命令行自变量题会另开笔记!!!