C语言———指针进阶4

目录

9、指针和数组笔试题解析:

10、指针笔试题:


9、指针和数组笔试题解析:

sizeof(数组名)—— 如果sizeof里面单独放了一个数组名,那么该数组名表示整个数组的,计算的是整个数组所占空间的大小,单位是字节。

&数组名 —— 如果取地址符号后面单独放了一个数组名,该数组名表示整个数组的,取出的是整个数组的地址

除此之外,所有的数组名,都是数组首元素的地址

一、

//一维数组

1.int a[ ] = {1,2,3,4};
2.printf("%d\n",sizeof(a));
3.printf("%d\n",sizeof(a+0));
4.printf("%d\n",sizeof(*a));
5.printf("%d\n",sizeof(a+1));
6.printf("%d\n",sizeof(a[1]));
7.printf("%d\n",sizeof(&a));
8.printf("%d\n",sizeof(*&a));
9.printf("%d\n",sizeof(&a+1));
10.printf("%d\n",sizeof(&a[0]));
11.printf("%d\n",sizeof(&a[0]+1));

2.

sizeof(a)中的a是数组名,单独放在了sizeof内部,他就代表整个数组,计算的是整个数组所占空间的大小,一个int整型占4byte,整个数组里面有4个int整型,所以,整

个数组所占的空间大小就是4*4=16byte;

3.

sizeof(a+0),,其中,a是数组名,但不是单独放在sizeof内部的,因为sizeof中放的是a+0而不是单独的a,a这里也没有&符号,,所以a代表的是数组首元素的地

址,,所以a+0也相当于是数组首元素的地址,现在就是计算地址所占内存空间的大小,地址又是指针,指针就是地址,地址或指针所占空间的大小只和平台有关,32

位平台即X86下,大小就是4byte,64位平台即X64下大小就是8byte,要注意的是,此时没有解引用,所以a+0不是指的数组首元素的内容,而是数组首元素的地址。

4.

sizeof(*a),此处不是把数组名单独放在了sizeof内部,他是一个*a,又没有&数组名,,所以,这里的a就是数组首元素的地址,所以*a即对数组首元素地址进行解引

用,拿到的就是数组的首元素,而数组首元素的类型又是int整型,所以所占内存空间的大小就是4byte。

5.

sizeof(a+1),,其中,a是数组名,但不是单独放在sizeof内部的,因为sizeof中放的是a+1而不是单独的a,a这里也没有&符号,,所以a代表的是数组首元素的地

址,,数组首元素的类型又是int整型,所以a+1就会跳过一个int整型,即4byte,所以,a+1指的就是数组中第二个元素的地址,现在sizeof就是对地址进行计算所占内存

空间的大小,地址又是指针,指针就是地址,地址或指针变量所占空间的大小只和平台有关,32位平台即X86下,大小就是4byte,64位平台即X64下大小就是8byte,我

们一般常用的是都是X86平台即32位平台,所以结果4byte。

6.

sizeof(a[1]),a与[1]结合,这就是通过下标进行的访问,所以a[1]指的就是数组第二个元素,,,计算的是第二个元素所占空间的大小,而不是计算一个地址的大

小,,第二个元素是一个int整型,所以4byte;

7.

sizeof(&a),虽然不是单独的把数组名放在sizeof中,但是54444444胡让你爸在&符号后面单独放了一个数组名,即:&a,,a是数组名,在单独的一个数组名前面加

个&,所以,这里的a就是代表整个数组,取出的是整个数组的地址,即使是整个数组的地址,她仍是一个地址,,sizeof(&a)就是在计算一个地址的大小,计算地址

或指针变量的大小结果就是4或8byte,所以结果就是4byte;

8.

sizeof(*&a),,*的优先级高于&,但是两者中间没有数据,所以即使*的优先级高,也要先进行&取地址,然后整体再去解引用,现在sizeof中并不是单独的数组名,但

是,&后是单独的数组名,此时数组名代表的是整个数组,所以,&a取出的是整个数组的地址,该整个数组地址的类型是:int(*)[4],然后再去对整个数组的地址进行

解引用,拿到的就是整个数组,所以,*&a代表的就是整个数组,,sizeof(*&a)计算的就是整个数组所占内存空间的大小,已知,该数组有4个元素,每个元素都是int

整型,所以大小就是:4*4=16byte,其次一种方法就是:因为&和*可以相互抵消,,所以,sizeof(*&a)=== sizeof(a),就和上面的上面的分析是一样的,要知道的

是可以对指针变量进行解引用,也可以直接对地址进行解引用,如果是一个整型int类型的地址的话,,解引用就可以找到一个int整型,,一个字符类型的地址解引用就

可以找到一个字符,一个数组类型的地址,解引用就可以找到一个数组。

9.

sizeof(&a+1),这里的&a取出来的是整个数组的地址,该地址的类型是int(*)[4],整个数组的地址加1,就会找到跳过这整个数组然后找整个数组后面的数组的地

址,它仍是一个地址,就是计算一个地址的大小,,所以结果是4或8byte。

10.

sizeof(&a[0]),这里要考虑优先级,,因为[ ]的优先级高于&,,所以,a先和[0]结合,,指的就是下标为0的元素的内容,访问的就是数组首元素,然后再取地址,取

出的就是数组首元素的地址,再对一个地址计算所占内存空间的大小,那么结果就是4或者8byte;

11.

sizeof(&a[0]+1),前面的&a[0],这就是第一个元素的地址,首元素的类型是int整型,首元素的地址+1,就会跳过一个int整型,跳过4byte,指向第二个元素的地址,

然后计算地址的大小就是4或者8byte;

二、

我们所说的指针变量或地址的大小只和平台是什么类型有关,32位平台,指针变量大小就是4byte,64位平台,指针变量大小就是8byte,指针变量的大小只和平台类型

有关,和指针变量的类型是无关的,,不论你是什么样的指针类型,int*也好,char*也好,都没关系,结果都是4或8byte,只和平台有关,地址的大小也是一样,地址的

大小也是只和平台有关,和什么样类型的地址没有关系,,整型int的地址还是char字符型的地址,都没关系,只和平台有关,,因为指针变量就是地址,两者是一个东西;

char arr[] = {'a','b','c','d','e','f'};

printf("%d\n", sizeof(&arr+2));

这里的&arr取出来的是整个数组的地址,&arr+1,就是直接跳过这一个数组,就是跳过6byte,如果是&arr+2,就会跳过新的这一个数组,再跳过一个同样长度的数组,

就会跳过6+6=12个字节;

sizeof只关心要求的内容所占内存空间的大小,对于字符数组来说,不关心字符数组里面是否存在字符\0 ,,,sizeof只关注占用内存空间的大小,单位是字节,sizeof

不关注类型,什么类型都可以计算,strlen关注的是字符串中字符\0的位置,计算的是字符串中字符\0前面的字符个数,strlen只针对于字符数组和字符串,只能用来求字

符串或字符数组的长度,在此,sizeof是一个从操作符,而strlen是一个库函数,

三、

char arr[] = {'a','b','c','d','e','f'};
1.printf("%d\n", strlen(arr));

2.printf("%d\n", strlen(arr+0));

3.printf("%d\n", strlen(*arr));

4.printf("%d\n", strlen(arr[1]));

5.printf("%d\n", strlen(&arr));

6.printf("%d\n", strlen(&arr+1));

7.printf("%d\n", strlen(&arr[0]+1));

1.

因为这里没有sizeof和&,,所以这里的数组名arr就代表数组首元素的地址,把这个地址传给strlen函数,那么函数就会从这个地址对应的那个元素,就是字符a开始沿着

元素往后找,a,b,c,d,e,f,现在把整个数组里面的元素都找完了,还是没有发现字符\0,所以会出去该数组,在内存空间里面去找字符\0,但是在内存空间中,不

确定字符\0的确切位置,所以说,会返回一个随机值,由于内存空间在每次执行代码的时候都会被随机分配,所以每次返回的随机值都可能不同, 在本题中,返回的数

值是>=6的;

2.

因为这里没有sizeof和&,,所以这里的数组名arr就代表数组首元素的地址,则arr+0也是数组首元素的地址,就和上述情况是一样的,返回一个大于等于6的随机值。

3.

因为我们strlen函数是用来求字符串长度的,,我们用my_strlen函数来模拟实现strlen函数功能的时候,我们设定的函数my_strlen的返回类型是int,因为最后要知道字符

串长度为多少,,该数是一个int整型,,而我们函数的参数部分由于传过来的是一个地址,,所以要用指针来接收,,而我们又是对字符串求长度,所以该地址是一个

字符的地址,就要用一个字符指针来接收,,,即,char*str来接收的,做好用const来修饰一下,即:const char*str,,因为我们确认一下从那个元素开始数,但我们

不需要改变这个元素的内容,,所以用const放在*左边,,那样const就会固定str指向的那个元素的内容不会发生改变,但是不管用不用const修饰,,最终,都是一个

char类型的指针变量,,所以,对于strlen函数来说,,参数部分必须要传过来的是一个地址才可以,strlen需要的是一个地址,从该地址向后找字符,直到找到字符\0,

再统计字符的个数,strlen函数会进行其参数类型的检查。

我们这里的arr是数组首元素的地址,然后*arr,解引用找到的就是字符a,,把字符a传给strlen函数,strlen函数默认接收到的是一个地址,地址又是一串数字,所以,

他会认为传过来的是一个数字,因为字符a的ASCII码是97,,系统就会默认,把97看做一个地址,就是从起始地址为97的地方开始找字符\0,,对于strlen函数来说,,

参数部分必须为一个地址,,如果不是地址,就会出现错误,不管传过去的参数是什么东西,,一律会被strlen函数默认为该东西是一个地址;但是以97作为起始地址的

时候,该地址是非法的,该地址不是分配给我们的,所以系统就会报错,会形成内存访问冲突,系统就会报错为:在读取位置0x00000061时出现错误,,这是16进制数

字,,转为二进制就是00000000000000000000000001100001,对应的数字正好是我们的字符a的ASCII码97,就是把我们的97当做了一个地址;

4.

arr[1]代表的就是字符数组第二个元素'b',,把字符b传过去和把字符a传过去得到的结果是一样的。

5.

&arr取出来的是整个数组的地址,整个数组的地址和数组首元素的地址只是类型不同,但是所指的位置,即他们的值是相同的,该数组地址的类型应该是一个数组指针

类型,即: char(*)[6],这就是该整个数组的地址的类型,把整个数组的地址传给strlen函数,在strlen函数内部里,即strlen函数形参部分用的是const char*str,所

以,即使你是一个数组指针类型的char(*)[6],传过来,我strlen函数内部就会直接把他看成const char*的类型,,就比如强制类型转换一样,,现在把整个数组的地

址的类型转换为const char*的类型,,就是直接把那个整个数组的地址看成了数组首元素的地址了,,因为我们知道,,数组首元素的地址和整个数组的地址的值是一

样的,,二者指的是同一个地址,只是指向的这个地址两者的类型是不一样的,我现在把整个元素的地址换个角度看他,,虽然地址没有发生变化,因为是同一个地

址,但是换个角度看,他就是成了数组首元素的地址了,现在接收到了一个数组首元素的地址,所以结果还是一个随机值。

6.

&arr是整个数组的地址,&arr+1,就会直接跳过这整个数组,找到下一个数组的地址,,就是直接跳过一整个数组,直接来到这个数组最后一个元素,即字符f的后面,

指针指向字符f的后面,,然后把这个地址传给strlen函数,在strlen函数内部就会直接把该地址看成 const char*的类型,就会直接看成字符f后面那个元素的地址了,,就

不再是一个数组指针类型的地址了,同样也是,发生这种转变,所指的那个地址位置没发生改变,只是地址的类型发生了改变,现在strlen函数从该位置往后继续找字符

\0,,由于不知道字符\0的位置,所以还是一个随机值,但是,这个随机值和前三个随机值是不一样的,因为前面三个随机值已经把abcdef这6个字符长度算了进去,,后

面这个随机值一定比前三个随机值小6,如果我们把前三个随机值得出来的数看成一个随机值的话,,那么该随机值就可以理解成:随机值(前三个随机值得到的

数)-6,但是只看这一个结果的话,应该是一个大于等于0的随机值。

7.

&的优先级小于[ ],,所以arr[0]先结合,就代表访问下标为0的那个元素,就是字符a,取出字符a的地址,然后他是一个数组首元素的地址,加1就可以得到数组第二个

元素的地址,就是字符b的地址,,,我们现在得到b的地址,传给strlen函数,就是告诉函数,要从这个地址对应的那个元素,即字符b开始往后数,直到找到字符\0,

所以还是一个随机值,但是该随机值应该比前三个随机值数值小1,比第四个随机值的数值大5,单独看这一个结果的话应该是一个大于等于5的随机值。要知道,如果执

行一次代码就会把上述几个值都打印出来的话,,那么执行一次代码,内存空间就会随机产生一些数据,在这一次中,内存空间中产生的数据是不会发生改变的,所

以,上述5个随机值之间会有一定的联系,,但是内存空间每次分配数据的时候,本质上是会随机分配的,每次执行代码就会分配不一样的数据,但是在同一台电脑上可

能执行多次代码的时候,内存空间分配的数据是一样的,,在不同的电脑上,内存空间数据的分配可能是不同的。

四、

char arr[6] = {'a','b','c','d','e','f'};

这种类型的初始化就是,数组里面能够看到的数据,就是所有的数据,没有隐藏的字符 \0;

char arr[7] = "abcdef";                                     a b c d e f \0 , \0算一个字符

如果是使用常量字符串去初始化字符数组的话,在字符串的结尾处会隐藏着一个字符\0,作为字符串结束的标志。

char arr[ ] = "abcdef";
//此时就是使用常量字符串去初始化字符数组的话,在字符串的结尾处会隐藏着一个字符\0,作为字符串结束的标志。

1.printf("%d\n", sizeof(arr));

2.printf("%d\n", sizeof(arr+0));

3.printf("%d\n", sizeof(*arr));

4.printf("%d\n", sizeof(arr[1]));

5.printf("%d\n", sizeof(&arr));

6.printf("%d\n", sizeof(&arr+1));

7.printf("%d\n", sizeof(&arr[0]+1));

8.printf("%d\n", strlen(arr));

9.printf("%d\n", strlen(arr+0));

10.printf("%d\n", strlen(*arr));

11.printf("%d\n", strlen(arr[1]));

12.printf("%d\n", strlen(&arr));

13.printf("%d\n", strlen(&arr+1));

14.printf("%d\n", strlen(&arr[0]+1));

1.

此处没有&,但是把数组名单独放在了sizeof中,所以,此时的数组名代表的是整个数组,计算的是整个数组所占内存空间的大小,由于字符串末尾还有一个隐藏的字符

\0,所以,该字符数组中一个有7个元素,每个元素都是char类型,占1byte,所以一共占:7*1=7byte。

2.

此时得数组名代表数组首元素的地址,arr+0还是数组首元素的地址,是地址或指针变量所占内存空间的大小就是4或8byte。

3.

此时没有&,也没有单独放在sizeof中,所以数组名arr就是数组首元素的地址,然后解引用就拿到了数组首元素,即字符'a',又因字符a的类型是char,所以占内存空间

的大小就是1byte,还可以把*arr看做*(arr+0),再看做是arr[0],所以指的就是数组首元素。

4.

arr[1]就是数组第二个元素,计算方法和上题是一样的。

5.

此时没有把数组名单独放在sizeof中,但是&后面是单独的数组名arr,所以,取出的就是整个数组的地址,仍是一个地址,计算地址或指针变量的大小就是4/8byte。

6.

此时,没有把数组名单独放在sizeof中,但是&后面是单独的数组名arr,所以,取出的就是整个数组的地址,&arr+1就会跳过一个数组,指向下一个数组,即下一个数组

的地址,仍是一个地址,所以结果还是4/8byte。

7.

此时,[ ]的优先级高于&,,所以,arr和[0]先结合,指的就是数组首元素,然后整体进行取地址,&arr[0],得到的就是数组首元素的地址,数组首元素的类型又是char类

型,+1就会跳过一个char类型,所以就是数组第二个元素的地址,仍是一个地址,结果还是4/8byte。

8.

此时,没有sizeof也没有&,,所以arr就是数组首元素的地址,是一个地址传给strlen是可以的,从该地址往后找字符\0,并统计字符\0前面得字符的数量,不包括字符\0

,所以结果是6。

9.

此时,没有sizeof也没有&,,所以arr就是数组首元素的地址,所以arr+0还是数组首元素的地址,和上题是一样的答案。

10.

此时,没有sizeof也没有&,,所以arr就是数组首元素的地址,*arr找到的就是数组首元素,即字符a,把字符a传给strlen函数是不允许的,因为不是一个地址,形成非法

访问内存,结果是error。

11.

arr[1]指的就是数组第二个元素,类型是char类型,不是一个地址,所以和上题一样会形成非法访问内存,结果是error。

12.

此时,没有sizeof,但是在&后放了数组名,所以该数组名代表整个数组,取出的是整个数组的地址,,该地址的类型是char(*)[7],把该地址传给strlen函数,类型就

会被强制类型转为const char*,然后从该地址往后找字符\0,,结果还是6byte。

13.

此时,没有sizeof,但是在&后放了数组名,所以该数组名代表整个数组,取出的是整个数组的地址,,该地址的类型是char(*)[7],,&arr+1就会跳过一整个数组,代

表的就是下一个数组的地址,从该位置往后找字符\0,但是,已经出了字符数组,所以继续在内存空间中找字符\0,则会返回一个大于等于0的随机值。

14.

由于[ ]的优先级高于&,,所以arr[0]结合代表着是数组首元素,然后整体取地址,得到的就是数组首元素的地址,&arr[0]+1得到的就是数组第二个元素的地址,从该位

置往后找字符\0,所以答案就是5;

五、

char *p = "abcdef";

1.printf("%d\n", sizeof(p));

2.printf("%d\n", sizeof(p+1));

3.printf("%d\n", sizeof(*p));

4.printf("%d\n", sizeof(p[0]));

5.printf("%d\n", sizeof(&p));

6.printf("%d\n", sizeof(&p+1));

7.printf("%d\n", sizeof(&p[0]+1));

8.printf("%d\n", strlen(p));

9.printf("%d\n", strlen(p+1));

10.printf("%d\n", strlen(*p));

11.printf("%d\n", strlen(p[0]));

12.printf("%d\n", strlen(&p));

13.printf("%d\n", strlen(&p+1));

14.printf("%d\n", strlen(&p[0]+1));

如果把一个常量字符串放在一个指针变量中去,,那么,就会把这个字符串首元素的地址放在这个指针变量中去,,同时还要知道,该常量字符串的内容不能改动,但

是如果把一个常量字符串放在一个字符数组里面的时候,它的字符串内容是可以进行改动的,这两种要区分开。

1.

字符指针变量p里面存放的就是常量字符串首元素,即字符a的地址,因为字符指针变量p是一哥指针变量,现在sizeof(p)就是来求一个指针变量的所占内存空间的大

小,结果就是4/8byte。

2.

字符指针变量p的类型是char*,,所以p+1就会跳过一个char类型,又因,字符指针变量p里面存放的是字符串首元素的地址,即字符a的地址,所以,p+1代表的就是字

符串第二个元素的地址,即字符b的地址,仍是一个地址,所以结果就是4/8byte。

3.

字符指针变量p里面存放的是字符串首元素的地址,即,字符a的地址,然后对p解引用就可以拿到字符串的首元素,即拿到了字符a,又因为字符a的类型是char类型,,

所以计算出来的结果就是1byte。

4.

p[0]=*(p+0),字符指针变量p里面存放的是字符串首元素的地址,p+0还是字符串首元素的地址,那么*(p+0)拿到的就是字符串的首元素,其类型是char,再计算大小

就是1byte。

5.

字符指针变量p也是一个变量,所以也在内存里面开辟空间,所以字符指针变量p也有地址,所以&p也是地址,对地址进行sizeof计算出来的就是4/8byte,因为字符指针

变量p的类型是char*,,所以,&p的类型就是char**,,是一个二级指针。

6.

因为,由上题可知,&p也是一个地址,该地址的类型就是char**,所以,&p+1就会跳过一个char*的类型,代表的就是下一个char*类型的元素的地址, 仍是一个地址,

所以,在使用sizeof时就计算出的结果就是4/8byte,和类型无关。

7.

由于[ ]的优先级高于&,所以,p先和[0]结合,等价于*(p+0),,p又是字符串首元素的地址,p+0还是字符串首元素的地址,再对他整体解引用得到的就是字符串的首

元素,即到了字符a,然后对p[0]整体进行解引用,得到的就是字符串首元素的地址,又因为字符串首元素的类型是char类型, 所以,&p[0]+1就会跳过一个char类型,

得到了字符串第二个元素的地址,即字符b的地址,仍是一个地址,所以计算出来的结果还是4/8byte。

8.

字符指针变量p里存放的就是字符串首元素的地址,把该地址传给strlen函数,就从该地址往后找字符\0,,所以结果是6;

9.

字符指针变量p里存放的就是字符串首元素的地址,字符指针变量p的类型是char*,,所以p+1就会跳过一个char类型,又因字符指针变量p里存放的就是字符串首元素的

地址,所以p+1就是字符串中第二个元素,即字符b的地址,然后从该地址往后找字符\0,得到的结果就是5;

10.

因为p里面存放的是字符串首元素的地址,所以*p拿到的就是字符串的首元素,即拿到了字符a,字符a的类型是char类型,是一个变量,不是一个地址, 把他传给strlen

函数的话就会造成非法访问内存,所以会报错,

11.

此时的p[0]===*(p+0)===*p,所以和上题答案是一样的,

12.

由上面分析可知,&p是字符指针变量p的地址,该变量p仍会在内存中开辟空间,且该变量p的大小是4/8字节,这是由字符串首元素的地址的大小决定的,所以该变量p

的大小就是4/8byte,而字符指针变量p里面存放的又是字符串首元素的地址,由于字符串首元素的地址又是系统给随机分配的,我们并不知道字符串首元素的地址编号

里面是否存在字符\0,,并且出去该变量p之外的内存空间中的数据也是不确定的,所以,该结果就会返回一个随机值,

13.

由上题可知,&p是字符指针变量p的地址,它的类型是char**,所以,&p+1就会跳过一个char*类型,指向字符指针变量p的后一个char*类型的变量的地址,然后把该地

址传给strlen函数,由于他的地址类型是char**,传给strlen函数之后就会强制类型转换为const char*类型,然后从该处往后找字符\0,,但是相当于是在字符指针变量p

的所占内存空间的后面往后找字符\0,仍在整个内存空间块里面寻找字符\0,,所以计算出来仍是一个随机值,并且该随机值和上题中的随机值还没有关系,因为是不知

道字符指针变量p中的4个或者8个字节的内容是否存在字符\0。

14.

由于[ ]的优先级高于&,,所以p先和[0]集合,,等价于*(p+0)===*p,,然后整体再去解引用,发现,*和&抵消,就剩下了p,而字符指针变量p里面存放的又是字符

串首元素的地址,即字符a的地址,且p的类型是char*,,所以,&p[0]+1===p+1,,代表了字符串中第二个元素的地址,即字符b的地址,然后把改地址传给strlen函

数,从该位置往后找字符\0,出来的结果就是5;

六、

int a[3][4] = {0};

1.printf("%d\n",sizeof(a));

2.printf("%d\n",sizeof(a[0][0]));

3.printf("%d\n",sizeof(a[0]));

4.printf("%d\n",sizeof(a[0]+1));

5.printf("%d\n",sizeof(*(a[0]+1)));

6.printf("%d\n",sizeof(a+1));

7.printf("%d\n",sizeof(*(a+1)));

8.printf("%d\n",sizeof(&a[0]+1));

9.printf("%d\n",sizeof(*(&a[0]+1)));

10.printf("%d\n",sizeof(*a));

11.printf("%d\n",sizeof(a[3]));

1、

a是数组名,单独放在sizeof中,所以,代表的就是整个数组,计算的就是整个数组所占内存空间的大小, 所以则有:3*4*4=48byte。

2、

a是数组名,arr[0][0]就是通过下标访问二维数组中第一行第一列的元素,该元素是int整型, ,所以占4byte。

3、

对于该二维数组,要想访问第一行的话,应该是a[ 0 ][ j ],其中j=0,1,2,3,要想访问第二行的话,就是:a[ 1 ][ j ],其中j=0,1,2,3,同理第三行就是:a[ 2 ][ j ],其中

j=0,1,2,3,所以,a[0]就相当于是第一行的数组名,,a[1]就相当于是第二行的数组名,,a[2]就相当于是第三行的数组名,,所以现在的a[0]代表的就是第一行的数组

名,单独放在sizeof中,代表的就是整个数组,即,整个第一行,计算的就是整个数组的大小,即整个二维数组第一行所占内存空间的大小,,则有:4*4=16字节。

4、

a[0]代表的是二维数组第一行的数组名,没有单独放在sizeof中,且没有&,,所以就是数组首元素的地址,即,二维数组中,第一行元素的首元素的地址,该变量的类

型是int整型,,所以a[0]+1就会跳过一个int整型,指向二维数组中第一行元素中的第二个元素的地址,是地址,计算大小就是4/8byte。

5、

上题可知,a[0]+1指的就是二维数组中第一行中第二个元素的地址,然后整体解引用就拿到了二维数组中第一行第二个元素,该元素的类型是int整型,所以计算大小就

是4byte。

6、

因为,a是数组名,且没有单独放在sizeof中,并且也没有&,,所以a这里就代表了二维数组首元素的地址,,而二维数组首元素又是二维数组中的第一行,所以, a就

代表了二维数组中第一行的地址,它的类型是int(*)[4],所以,a+1就会跳过4个int整型,即跳过第一行指向了二维数组中第二行的地址,然后,仍是一个地址,计算

出来的大小就4/8byte。

7、

由上题可知,a+1就是二维数组中第二行的地址,那么对该整体解引用,拿到的就是该二维数组中的整个第二行,现在计算二维数组中整个第二行的大小, 就是

4*4=16byte,或者也可以理解成:因为,a+1就是二维数组中第二行的地址,那么对该整体解引用,拿到的就是该二维数组中的整个第二行,也就等价于二维数组中第二

行的数组名,即等价于a[1],,即:*(a+1)=== a[1],,然后sizeof(a[1]),其中,a[1]是二维数组第二行的数组名,单独放在了sizeof中,代表整个数组,计算的是

整个数组的大小,即,计算的是整个二维数组中第二行的大小,则还是4*4=16byte、

8、

由于[ ]的优先级高于&,,所以,a和[0]先进行结合,得到的是a[0]代表的是二维数组中第一行的数组名,,没有单独放在sizeof中,但是单独被取地址了,,所以&a[0]中

的a[0]代表的就是整个数组,即,二维数组中的整个第一行,它的类型是一个整型数组指针,即int(*)[4],&a[0]+1就会跳过4个int整型,即跳过第一行,得到的就是二

维数组中整个第二行的地址,是地址,计算出来的大小就是4/8byte。

9、

由上题可知,&a[0]+1指的就是二维数组中整个第二行的地址,对该地址解引用就可以拿到整个第二行,计算整个第二行的大小就是4*4=16byte,或者可以理解成:

&a[0]+1指的就是二维数组中整个第二行的地址,对该地址解引用就可以拿到整个第二行,就相当于是二维数组整个第二行的数组名,即:*(&a[0]+1)=a[1],,就是

sizeof(a[1]),,a[1]是第二行的数组名,单独放在sizeof中,代表的就是整个第二行,计算出来的就是二维数组中整个第二行的大小,则有:4*4=16byte。

10、

a是二维数组的数组名,没有单独放在sizeof中,也没有单独被取地址,所以,a在这里就是二维数组首元素的地址,即二维数组整个第一行的地址,然后解引用就可以拿

到整个第一行,计算整个第一行的大小就是4*4=16byte,,或者是,a在这里就是二维数组首元素的地址,即二维数组整个第一行的地址,然后解引用就可以拿到整个第

一行,就相当于是拿到了二维数组第一行的数组名,即:*a===a[0],即,sizeof(a[0]),a[0]是二维数组第一行的数组名,单独放在sizeof中,代表整个第一行,计算的是

整个第一行的大小,就是4*4=16byte,即:*a===*(a+0)===a[0];

11、

a[3]看起来像是越界访问了,,但是,没有关系,属性分为值属性和类型属性,而在sizeof内部,不会进行值属性的运算,即不会真正计算,只进行类型属性的运算,

即,sizeof只根据他的类型就可以得出结果,不需要知道他的值到底是多少, 比如:int a=10,,就可以写成:sizeof(a)和sizeof(int),,前者看起来是需要值才可

以得出来结果的,其实不然,sizeof这个操作符是根据a来推出a得类型,然后去计算该类型所占内存空间的大小的,即,你即使把值给我传了过来,我也没有进行真正的

运算,只是根据这个值来推导出它的类型,即推导出后者再得出结果的,所以,不会真的计算的意思就是不会真的取访问a这个变量中的数据到底是多少,就比如这里的

sizefo(a[3]),sizeof该操作符内部不会真正的去访问a[3],所以就不存在越界访问这个问题,只不过是根据a[3]来推导出他的类型,然后最终得出结果,又因为我们知

道,a[3]是二维数组第四行的数组名,单独放在sizeof中,代表的就是该二维数组中第4行,即代表的是整个数组,,所以,第四行,即a[3]的类型就是int [4],,然后再

根据该类型推导出最后的结果,

即sizeof(a[3])===sizeof(int [4]),,里面是一个整个一维数组,,该数组有4个元素,且每个元素类型都是int整型的数组,,所以,根究该类型就可以知道,最后的结

果应该是:4*4=16byte。

比如:

s=a+6,,这里是s说的算,,你这里的a是4,即int整型类型,,6也是int整型类型, ,所以两者可以进行加法运算,得出来的结果是10,也是int整型,,现在要把这个

int整型放在s里面,,而s是短整型,占2byte,,,这里是s说的算,,所以,我的10是占4byte,放不进去short类型里面,就会进行截断,截断成2byte,然后再存在s里

面,对于sizeof(s=a+6)来说,,sizeof()这个操作符看的就是s=a+6这个表达式的最终值,就是我们这里的s,,因为s是short短整型,所以计算短整型的大小就是

计算:sizeof(short),,因为short占2byte,所以计算出来的结果就是2byte,,但是我们这里的sizeof操作符,他是通过最后的结果,即s的类型来计算出s这个类型所

占空间的大小,,sizeof函数内部并没有进行真正的计算,,即s=a+6,这个表达式根本就没有进行计算,,,所以说,s的值,根本就没有发生变化,s的值还是5,,

现在以 %d 的形式打印s,而s占2byte,所以要进行,整型提升,对于短整型的s,值为5来说,它的二进制序列,即内存中的二进制序列,即补码,short占2byte,

16bit,,,因为5是正数,且原码为:0000000000000101,因为5是正数,,所以补码也是0000000000000101,,因为short是有符号的short,所以,有符号位,不

够%d,所以要补到32bit,高位补符号位,即补0,得到:00000000000000000000000000000101,,这就是以%d,有符号,所以最高位为符号位,,0代表正数,现

在的00000000000000000000000000000101是补码,,又因为正数,原码也是00000000000000000000000000000101,,打印出来的结果还是5;

我们平时见得类型都是有符号位的类型,比如char,int,short等等,,这都是有符号位的类型,而对于

unsigned char,unsigned int,unsigned short,这都是无符号位的类型,而无符号位的类型在整型提升的时候用的都是高位补0;

同理,,如果在sizeof()操作符里面放置一个函数,,,我们知道,,函数调用也是表达式,在sizeof内部不会去执行这个表达式的,所以该函数不会被调用的,但是

函数的返回值的类型可以被我们这里的sizeof计算出来,比如我把函数的返回值,设为5,放在了sizeof内部,因为5是int整型,,所以计算出来的结果应该是4byte;

10、指针笔试题:

一、

int main()
{
    int a[5] = { 1, 2, 3, 4, 5 };
    int *ptr = (int *)(&a + 1);
    printf( "%d,%d", *(a + 1), *(ptr - 1));
    return 0; }
//程序的结果是什么?

首先,a是数组名,是一个一维数组的数组名,&a,此处的a代表整个数组,取出来的是整个数组的地址,整个数组的地址,应该放在一个数组指针类型的变量里面去,

该变量的类型就是:int(*)[5],,所以说,&a的类型就是一个数组指针类型,,现在,&a+1,就会跳过整个数组,直接到下一个数组,,即直接到原数组最后一个元

素的后面,下一个数组的起始位置,,但是它仍然还是一个数组的地址,即第二个数组的地址,,对于数组地址来说,,它的类型就是数组指针类型,,现在要把一个

数组的地址,即数组指针类型的地址放在一个int*类型的变量ptr里面,肯定是不行的,类型不匹配,所以要强制类型转换成int*类型,这时候只是进行了类型的转换,,

本来的那个地址的值是没有发生变化的,,所以还是指向该数组最后一个元素的后面,下一个数组的起始位置,,现在,ptr里面存放的地址就是该数组最后一个元素的

后面,下一个数组的起始位置,但是是int*类型,,现在ptr-1,就会往前跳过一个int整型,即跳过4byte,就是找到了数组中整型元素5的地址,然后解引用,就找打了5

这个数字,,同时,前面的a,没有&,也没单独放在sizeof里面,,所以a是数组首元素的地址,且数组首元素的类型是int类型,a+1就跳过一个int整型,找到元素2的地

址,然后解引用就找到了元素2,,,所以打印出来是2,5、

对于一个指针变量来说,,比如,int(*p)[5],int*ptr,,还是char*arr,,对于指针变量来说,,不管你是什么类型的指针变量,只要去掉指针变量的名字,即去掉,

p,ptr,arr,剩下的内容就是指针变量的类型,,所以,int(*)[5],就是数组指针变量p的类型,int*,就是整形指针变量ptr的类型,,char*,就是字符指针变量arr的

类型;

二、

指针类型决定了指针的运算,,此处的运算指的是加减整数的运算,

如果在代码中输入一个数字,仅仅输入一个数字的话,就会被系统默认为是一个int整型数字,占4byte,不会默认成其他类型。

因为变量p的类型是结构体指针类型,即:变量p的类型是:struct Test*,,现在又告诉了变量p的值为:0x100000,这就相当于是在代码中输入了一个数字,并被系统

默认为是int整型,由于是int整型数字,则占4byte,此时的十六进制以及其他进制仅是整型数字int的表达形式而已,不管那种形式,都是占4byte,对于十六进制表示形

式来说,4byte就要用8个十六进制数字来表示,因为一个十六进制的数字占4位,所以此处给的0x100000,如果补全的话,应该是0x00100000,而此时的变量p是一个

结构体指针变量,所以该结构体指针变量里面存放的内容应该是地址,但是,仅仅告诉了变量p的值为0x00100000,中的0x00100000,他就会被系统认为是一个十六进

制数字,该数字并不能直接作为一个地址来看,,,如果想把某个数字看做地址的话,就要先进行强制类型转换,把该数字转换成指针变量p的类型,即把十六进制数字

转换成struct Test*类型,才能被当做是结构体指针变量p的地址,所以,题中告诉的,假设p的值为0x00100000等价于p=(struct Tset*)(0x00100000);

struct Test,这是一个结构体类型,,如果在结构体类型后面加个*p,所以,对于p来说,他就是一个结构体指针变量,类型是结构体类型;

一个int整型的大小是4byte,一个char字符型的大小是1byte,double浮点型的大小是8byte,,但是我们不知道一个结构体所占内存空间的的大小是多少,还没有学习,

现在,题目告诉了一个结构体所占内存空间的大小是20byte,这20byte是指,,结构体要存,int Num,char*pcName.....short sBa[4],,把这些存进去总共占20byte,

如果整型int类型的数字没有特殊表明,就默认是10进制的数字,平常输入的整型赋值为10,20,等,这都是10进制的数字,,现在给了个0x1,我们平时看到的数字都是

整型int,占4byte,写成16进制应该也是占4byte,我们知道,0x1就是一个16进制的数字,,0x代表十六进制,1代表一个16进制的数字,十进制,二进制,八进制,十

六进制仅是整型数字的表示形式,整型数字占4byte,补齐应该是 0x 00 00 00 01,,这才是16进制整型数字1应该的写法,这是16进制的数字,我们首先把它转换为10

进制的数字,首先转为二进制得:00000000000000000000000000000001,,这是内存中的二进制序列,就是补码,最高位符号位0,正数,,所以,原码也是这个,

结果十进制的值就是1,,我们知道,对于十进制和十六进制,1-9都是一样的,,十进制的10-15,在16进制中是,a,b,c,d,e,f,,所以,0x1就是代表十进制中

的1,我们平常写的数字都默认是十进制,所以 p+0x1,就可以看成p+1;

我们知道这里的p就是一个结构体指针变量,,对于指针变量的加减运算,取决于指针变量的类型,,

因为p是结构体指针变量,它的类型就是:struct Test*类型,,p+1,就会直接跳过一个结构体,直接指向下一个结构体的起始位置,因为整型int占4byte,所以他会直接

访问4byte,char字符占1byte,所以他会直接访问1byte,现在我们知道,该结构体占20个byte,所以,p+1,就会直接跳过一个结构体,就会跳过访问20byte,我们又

知道,一个字节分配一个地址,那这里跳过20byte,就会跳过20个地址,就相当于在原来p的地址上加上20个字节,即20个地址,这里的20是十进制,我们地址是以二

进制存储,十六进制展示的,,所以说,十进制20要转为二进制,即:00000000000000000000000000010100,这是二进制的10,,转为16进制,每4bit组一个小组,

补全得:0000 00000 00000 00000 0000 0000 0001 0100,,所以转为16进制就是14,这不是十四,而是一四,,加到0x00100000后得:

0x00100000

0x00000014

0x00100014;

所以第一个结果就是:0x00100014;

指针变量p本来的类型是结构体指针类型,现在把结构体变量p强制类型转换为 unsigned long类型,就是一个无符号的长整型的类型,此时变量p不再是一个结构体指针

变量了,只是一个无符号长整型变量了,此时就把无符号长整型变量p中的值当做是一个数字了,比如:int* p=&a,,因为整形指针变量p的类型是int*,所以,p+1就会跳

过1个int整型,跳过4byte,,但是,如果是 unsigned long p=10 ,,p+1的话,得到的结果就是11,,unsigned long 即为长整型,是一个整型,此时p中的值还是

0x00100000,但是该十六进制数字仅仅被认为是一个十六进制数字,它的类型就是unsigned long,就不再是struct Tset*类型的了,,0x00100000代表的是一个十六进

制无符号长整型数字,现在要在该数值上加上0x01,对于十进制的表示形式就是单纯的加上1即可,对于十六进制表达形式就是单纯的加上0x00000001即可,即加上:

0x00 00 00 01,,,得到的就是:0x00100001、

下面:

最初,p是一个结构体指针变量,它的类型是struct Test* 类型,,如果该指针变量p+1,就会跳过一个结构体类型, ,,因为该结构体是20byte,所以就会访问20byte,

现在,强制类型转换为:unsigned int*,即无符号的整形指针,,所以现在的p仍是一个指针变量,只不过是一个无符号的整型指针变量,它的类型是unsigned int*,,

无符号整形指针变量+0x1,即相当于加上十进制的1,,相当于:p+1,p是无符号的整型指针变量,p+1就会跳过一个int整型变量, 就会访问4byte,就相当于在p的地

址上加上4byte,现在的4是十进制的4,转化为十六进制,还是4,即十六进制的4,即:0x00 00 00 04,,所以,最后结果为:0x00100004;

整形指针变量+1就会跳过一个int整型, 跳过4byte,字符指针变量+1就会跳过一个char字符类型,跳过1byte,对于int整数加1,就是加1,比如,50+1,就是51,不在

考虑跳过几个字节,对于无符号的长整型 unsigned long类型的整数加1,也是+1,和int整型加1是一样的,无论是对于有符号的int,还是无符号 unsignen int,都是int,

占4byte,都是占4字节,,和有无符号没有关系;

%p是用来打印地址的,以地址的形式进行打印,按照十六进制的表示形式进行打印,要打印地址的话就要打印全部的十六进制数字,所以即使高位不起作用的0也不会

被省略,如果使用%#p打印的话,就不会把0x去掉,也不会把高位的0,即无作用的0扔掉。

%x就是用来打印十六进制的,和%p打印地址不同,他会把高位不起作用的0会被省略掉,以最简的形式展现,两者打印出来的都是十六进制的表示形式,并且都会把0x

省略掉。如果是%#x打印的话,,就不会把0x去掉,,但是高位的0,,,无用的0是会被扔掉的;

地址的本质是数值,二进制,八进制,十进制,十六进制等,都是数值的表示形式,所以地址可以使用这些表示形式来表示,地址原本是使用二进制进行表示的,但

是,已知地址需要使用%p进行打印,而%p是按照十六进制的表示形式进行打印的,所以当使用%p对地址进行打印的时候,出来的结果是以十六进制的形式来表示的,

但不代表地址只能使用十六进制来表示,而是使用其他进制一样可以进行表示。

如果一个十六进制的数字加上一个十进制的数字,最后要求得到一个十六进制的数,有两种思路:

1.把这个十六进制的数字转为十进制,,与另一个十进制相加,求出来的和再转为十六进制;

2.把这个十进制的数,直接转为十六进制,然后与我们已知的十六进制的数直接相加就可以了;

三、

int main()
{
    int a[4] = { 1, 2, 3, 4 };
    int *ptr1 = (int *)(&a + 1);
    int *ptr2 = (int *)((int)a + 1);
    printf( "%x,%x", ptr1[-1], *ptr2);
    return 0; }

对于ptr1所在的一行代码来说,a是数组名,没有单独放在sizeof中,但是,单独被取地址,所以,a在这里代表的是整个数组,取出的是整个数组的地址,该地址的类型

是int(*)[4],,然后&a+1就会跳过一整个数组,指向整型int类型数据4的后面,此时&a+1的类型还是int(*)[4]的类型,现在整体被强制类型转化为int *类型,存放在

整型指针变量ptr1里面,又因,ptr1[-1] === *(ptr1+(-1)) === *(ptr1-1),,因为ptr1的类型是int* ,所以,ptr1-1就会往前跳过一个int整型,此时指向了整型数

据,4的地址,然后整体再被解引用就拿到了整型数据4,但是,该4是十进制表示形式,而%x打印的是十六进制,并且能够把高位不起作用的0省略掉,所以要先把十进

制数字4转为十六进制的数字4,即得到了:0x00 00 00 04 ,现在使用%x进行打印,就会把高位的不起作用的0省略掉而且把0x省略掉,所以最终打印在屏幕上的就是4;        

对于ptr2所在的一行代码来说,a是数组名,没有sizeoif和&,所以a代表的就是数组首元素的地址,又因为该地址的类型是int* 类型,,并且强制类型转换的优先级高于

+,,所以,先对a进行强制类型转换为int类型,而已知,对于地址或指针变量的大小来说,一般都是4/8byte,但是我们使用的电脑一般都默认地址或指针变量的大小是

4byte,并且地址都是使用%p进行打印的,%p又是按照十六进制的形式进行打印的,且不会把高位不起作用的0省略掉,所以,再此就假设数组首元素的地址为:0x00

00 00 10,,在强制类型转换之前,该地址的类型是int*,,但是强制类型转换之后,就变成了int类型,int是整型,对于整型的0x 00 00 00 10来说,他就是一个十六进

制数字,所以(int)a就是一个十六进制数字,现在(int)a+1,就相当于是给十六进制数字加上一个十进制数字1,而十进制数字1转为十六进制的数字1就是:0x 00

00 00 01,,所以(int)a+1 =0x 00 00 00 11,,然后0x00 00 00 11就是一个十六进制数字,它的类型还是int类型,现在整体被强制类型转换为int*类型,,所以,该

十六进制数字就会被认为是一个地址,该地址的类型是int*类型, 把该地址存放在整型指针变量ptr2中,现在观察,ptr2中存放的地址相比于数组首元素的地址就相当于

是往后偏移了一个字节,因为一个字节代表一个地址,所以说,地址+1就代表往后偏移了一个字节,假设是小端存储,所以,数组在内存中的存储顺序应该是: 01 00

00 00 02 00 00 00 03 00 00 00 04 00 00 00 ,,其中,a是数组首元素的首地址,即指向了字节01 的前面,现在(int)a+1 作为地址就是往后偏移了一个字节,即指向

了字节01 后面的一个字节00,,又因为ptr2的类型是int*类型,,所以,解引用就会访问4个字节,访问出来的结果就是:00 00 00 02,又因是小端存储,,访问到的只

是在内存中的,读取出来就是 0x 02 00 00 00,,现在以%x打印,他会省略掉高位不起作用的0,并且把0x省略掉,所以打印在屏幕上的结果应该是:2 00 00 00,,所

以最终的两个结果是: 4 、2000000 。

四、

#include <stdio.h>
int main()
{
    int a[3][2] = { (0, 1), (2, 3), (4, 5) };
    int *p;
    p = a[0];
    printf( "%d", p[0]);
 return 0; 
}

c语言中逗号运算符和逗号表达式

C语言提供一种特殊的运算符——逗号运算符,用它将两个表达式连接起来。

如:  3+5,6+8称为逗号表达式,又称为“顺序求值运算符”。逗号表达式的一般形式为:  

     表达式1,表达式2

逗号表达式的求解过程是:先求解表达式1,再求解表达式2。整个逗号表达式的值是表达式2的值。

+的优先级高于逗号 所以,+先进行运算,3+5和6+8先进行运算,然后得到的两个值又是逗号表达式

例如,上面的逗号表达式“3+5,6+8”的值为14。

又如,逗号表达式  a=3*5,a*4 对此表达式的求解,读者可能会有两种不同的理解:

一种认为“3*5,a*4” 是一个逗号表达式,先求出此逗号表达式的值, 如果a的原值为3,则逗号表达式的值为12,将12赋给a, 因此最后a的值为12。

另一种认为:“a=3*5”是一个赋值表达式”,“a*4”是另一个表达式,二者用逗号相连,构成一个逗号表达式。这两者哪一个对呢?

对于3*5来说,赋值运算符的优先级别高于逗号运算符, 因此应先求解a=3*5(也就是把“a=3*5”作为一个表达式)。经计算和赋值后得到a的值为15,然后求解a*4,得60。

整个逗号表达式的值为60。 

一个逗号表达式又可以与另一个表达式组成一个新的逗号表达式,如(a=3*5,a*4),a+5

()的优先级高于逗号,先进行()内的操作,把()看成一个整体,所以这就是一个逗号表达式,然后,()里面的内容,又是一个逗号表达式,我们知道=和*的优

先级高于+,所以,括号里面是一个逗号表达式,两个表达式分别为a=3*5和a*4,

先计算出a的值等于15,再进行a*4的运算得60,此时,逗号表达式得出来的结果是60,但是我们后面用到的是a+5即用到a的值,,,,我们的60是逗号表达式的值,但

并没有赋值给我a,所以,a的值仍是前面的15,,(但a值未变,仍为15),再进行a+5得20,即整个表达式的值为20。  

逗号表达式的一般形式可以扩展为:

     表达式1,表达式2,表达式3……表达式n —— 它的值为表达式n的值。  

逗号运算符是所有运算符中级别最低的。

因此,下面两个表达式的作用是不同的:  

① x=(a=3,6*3)  ② x=a=3,6*a  

第①个是一个赋值表达式,将一个逗号表达式的值赋给x,x的值等于18。

()的优先级高于=,把()内看成一个整体,然后,括号内又是一个逗号表达式,且两个表达式分别为:a=3和6*3,,因为=和*的优先级都高于逗号;

第②个是逗号表达式,它包括一个赋值表达式和一个算术表达式,x的值为3。 

对于a两边都是=,不用管,对于3和6,=和*优先级都高于*,,所以,是逗号表达式,且两个表达式分别为:x=a=3和6*a 

其实,逗号表达式无非是把若干个表达式“串联”起来。在许多情况下,使用逗号表达式的目的只是想分别得到各个表达式的值,而并非一定需要得到和使用整个逗号表达

式的值,逗号表达式最常用于循环语句(for语句)中. 

请注意并不是任何地方出现的逗号都是作为逗号运算符。例如函数参数也是用逗号来间隔的。

如 printf("%d,%d,%d",a,b,c);  

上一行中的a,b,c并不是一个逗号表达式,它是printf函数的3个参数,参数间用逗号间隔。

如果改写为printf( " %d,%d,%d " ,(a,b,c),b,c);则“(a,b,c)”是一个逗号表达式,它的值等于c的值。括弧内的逗号不是参数间的分隔符而是逗号运算符。括

弧中的内容是一个整体,作为printf函数的一个参数,前面的 '' '' 里面的内容的逗号不算逗号表达式;

逗号表达式的存在不一定必须有();

C语言表达能力强,其中一个重要方面就在于它的表达式类型丰富,运算符功能强,因而c使用灵活,适应性强

首先,我们要看到,二维数组初始化里面是(),而不是{ },所以对(0,1),(2,3),(4,5)来说,这三个括号外面的两个逗号是这个二维数组分隔元素的分隔符,

而不是逗号表达式,,我们又发现,(0,1)和(2,3)和(4,5)这三个整体作为二维数组的一个元素,但他们不是用{ }来括起来的,,如果用{ }括起来,说明这一个整

体就是我们二维数组的一个元素,这样正好对应我们的行数和列数,,但是现在,不是用的{ },,而是括号,就不能再把他们三个整体看做二维数组的元素,所以,就

是三个逗号表达式,()代表是一个整体,,所以,(0,1)===1,,,(2,3)===3,,(4,5)===5,,所以:

int a[3][2]={1,3,5};

又因为我们是三行两列,,所以,不够的地方的元素用0来补充,,

1 3

5 0

0 0

这才是我们二维数组的面目,,现在,我们又知道,,a[0]是二维数组第一行的数组名,,既没有放在sizeof里面,也没有&,,所以,数组名就相当于数组首元素的地

址,所以,p里面放的就是二维数组第一行首元素的地址,即1的地址,1是一个int整型,即一个整型的地址,而指针变量p的类型正好也是一个整形指针类型,,所也正

好可以放进去,,我们的p[0]===*(p+0),,所以,p里面放的是二维数组第一行首元素的地址,,而且指针变量p是一个int*类型的,,所以,p+1就会直接跳过一个整

型,访问4byte,但是现在是p+0,所以p+0还是指向1的地址,,然后解引用并以%d打印,,就可以打印出来数字1;

五、

int main()
{
    int a[5][5];
    int(*p)[4];
    p = a;
    printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
    return 0;
 }

a是一个二维数组,5行5列,,在p=a这里,没有&,也没有sizeof,,并且,a是数组名,,a代表二维数组首元素的地址,又因为a是一个二维数组,,所以,代表二维

数组第一行的地址,,它的类型应该是,,int(*)[5],,但是,,int(*p)[4],,说明, p是一个数组指针变量,他指向一个数组,该数组有4个元素,每个元素的类

型都是int整型,,现在p=a,,把a赋值给p,,,a的类型就是int(*)[5],而p的类型是int(*)[4],,类型不一样,在这里就相当于是强制类型转换,由于是不同类型

的,并且没有进行强制类型转换,可能出现警告,但是可以正常运行,,如果想要不被警告,,就加上强制类型转换就可以了;

即:p=a 改写成 p =(int(*)[4]) a ,,, 现在,a是指二维数组第一行的地址,把a放在数组指针p里面,,所以,p指向的地方就是二维数组第一行的地址,,但是类型是

int(*)[4],,即,数组指针类型,,如果p+1,就会直接跳过一个数组,该数组有4个元素,每个元素都是int整型,,p的类型是int(*)[4],,p+1就会直接跳过一个数

组,,所以直接跳过4个整型,对于,&p[4][2]来看,[ ]的优先级高于&,先看p[4]===*(p+4),,把p[4]看做一个整体,,就可以得到: *(*(p+4)+2),所以,

p[4][2] === *(*(p+4)+2),数组指针变量p指向二维数组第一行的地址,类型是int(*)[4],,所以,p+1就会跳过一整个数组,即,跳过4个int整型,16个字节,

p+4就会跳过4个整个数组,即跳过16个int整型,64个字节,因为数组指针变量p的类型是int(*)[4],所以,p+4的类型还是这个,所以,*(p+4)就拿到了一整个数

组,也相当于拿到了这个数组的数组名,*(p+4)在这里相当于是解引用后拿到的这个整个数组的数组名,在这里没有sizeof和&,所以就相当于是该整个数组的数组首

元素的地址,该整个数组首元素的类型又是int整型, 所以,*(p+2)+2,就会跳过两个int整型,8byte,然后再解引用就拿到了对应的那个元素,即,二维数组中第四

行第四列的这个元素,然后&p[4][2]就拿到了二维数组第四行第四列元素的地址,,又因,&a[4][2]中,[ ]的优先级高于&,所以就先进行a[4][2],就是二维数组通过下标

进行访问,即,第五行第三列的元素,再取出他的地址,现在有:&p[4][2]-&a[4][2],是低地址减去高地址,如果再以%d进行打印,出来的就是两个指针之间的元素个数

的相反数,结果就是 -4,,如果&p[4][2]-&a[4][2]直接以%p进行打印的话,我们知道,如果以%d打印出来就是 -4 ,所以根据 -4 写出在内存中的二进制序列,即补码,

因为 -4 的原码是:100000000000000000000000000000100,反码就是:1111111111111111111111111111111011,补码就是:11111111111111111111111111111100,,

现在要用%p来打印他的地址,就相当于是把补码的二进制序列以十六进制的形式进行打印,补码写成十六进制即为:0xFFFFFFFC ,,直接打印补码,不考虑它的原

码,%p打印会省略掉0x和高位不起作用的0,,所以,最后的结果就是:FFFFFFFFC,,

指针和指针相减的前提是,两个指针指向同一个空间;

六、

int main()
{
    int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    int *ptr1 = (int *)(&aa + 1);
    int *ptr2 = (int *)(*(aa + 1));
    printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));
    return 0;
}

aa是一个二维数组的数组名,,,&aa,aa就代表整个二维数组,,取出来的就是整个二维数组的地址,,&aa它的类型就是一个数组指针类型,,即,它的类型就是

int(*)[2][5],类型,,他指向一个2行5列的二维数组,数组里面每个元素的类型都是int整型,,现在,&aa+1,就会直接跳过一个二维数组,即直接跳过2行5列,跳

过10个元素,指针指向了元素10后面的那个地址,,现在强制类型转换为int*,只是类型发生了改变,,该地址,即指向的那个地方的位置时没有发生改变的,,,再把

这个地址放在整型指针变量ptr1里面,,下面的ptr1-1,,因为ptr1的类型是int*类型,,ptr1-1就会往前跳过一个整型,即跳过4byte,找到了元素10的地址,,然后解引

用就可以得到元素10,打印出来,

第二个里面的aa没有&,也没单独放在sizeof里面,,所以,这里的aa是数组名,而且代表数组首元素的地址,,所以是二维数组第一行的地址,,第一行可以看成是一

个 一维数组,aa的类型就是int(*)[5],,现在aa+1,就会直接跳过这个一维数组,指向第二行的地址,aa+1的类型也是

int(*)[5],,即指向元素6的地址,,这个地址是整个第二行的地址,然后解引用就可以得到整个第二行,相当于拿到了整个第二行的数组名,,整个第二行可以用数

组名aa[1]来表示,

就比如:int arr [5]={1,2,3,4,5};,,,我们就可以用arr这个数组名来表示这个数组,数组里面有5个元素,每个元素类型都是int整型,,因为对于一个数组来说,,去

掉数组名,剩下的就是这个数组的类型,,然后,我们上面,解引用就可得到整个第二行,,就可以用一个数组名来表示这整个第二行,即用aa[1]来表示整个第二行;

或者,,因为,*(aa+1)===aa[1],,也可以得出来aa[1];

所以,这里的(*(aa+1))===aa[1],,而aa[1],就相当于是第二行的数组名,,没有&,也没有sizeof,,所以代表数组首元素的地址, 即第二行首元素6的地

址,,本来就是int*类型,,所以这里的(int*),没起到作用,,就相当于把第二行数组首元素6的地址放在了ptr2里面,,又因

ptr2的类型是int*,所以,,ptr2-1就会跳过一个整型,跳过4byte,找到元素5的地址,解引用打印出来5;

 要注意,,第一个意思就是,,把&aa+1这个整体进行强制类型转换,,

第二行中,强制类型转换的优先级低于&,所以先进行&的操作,又因,强制类型转换的优先级高于+,所以,要先对&aa进行强制类型转换啊之后,整体再去+1。

七、

#include <stdio.h>
int main()
{
 char *a[] = {"work","at","alibaba"};
 char**pa = a;
 pa++;
 printf("%s\n", *pa);
 return 0; 
}

因为[ ]的优先级高于*,,所以,a和[ ]先结合成一个数组,该数组里面每个元素的类型都是char*,,所以是一个字符指针数组,数组里面每个元素都是字符指针类

型,,现在,初始化里面是三个字符串,,我们之前写过,,char *p=''abcdef'',,这是一个常量字符串放到字符指针变量中,,内容不可以发生改变,把一个常量字符

串放在一个字符指针里面,其实就是把字符串首元素,即字符a的地址放在字符指针变量p里面,,我们这里的数组也是一样的,,只不过是三种这样的情况;

对于这个字符指针数组来说,里面放置的就是work中w,at和alibaba中的a,这三者的地址,

a是数组名,没有&,也没有sizeof,,这里的数组名a就是代表,数组首元素的地址,就是这个字符指针数组首元素的地址,就是第一个char*类型的地址,,也就是字符

w的地址的地址,,就是一个一级指针的地址,,把一个一级指针的地址放在一个二级指针变量中,所以把第一个char*类型的地址放在一个字符二级指针变量pa中,它

的类型就是char**,,因为现在是,char**pa=a;,,这里的pa是一个字符二级指针变量,,我们知道,,如果是int*pa=arr,,pa+1就会跳过一个整型,就跳过

4byte,,其中,这里的*代表,pa是一个指针变量,,pa+1就会跳过一个int整型,,所以,因为这里的char**pa=a;,,后面的*代表我这里的pa是一个指针变量,,

pa+1或者pa++,就会跳过一个char*,,而我们的这个数组,每个元素的类型都是char*类型的,,刚开始,,a是数组首元素的地址,,所以就指向第一个char*类型的

元素的地址,,现在pa++,就会跳过一个char*类型,所以,就会指向我们数组第二个元素的地址,所以,,现在的pa指向的就是数组第二个元素的地址,,现在*pa,

就是解引用,就可以找到数组第二个元素,而数组第二个元素里面存放的就是字符串''at''里面的首元素的地址,就是字符a的地址,,现在我们就找到了一个字符串首元

素的地址,,现在如果以%s打印就可以直接把这个字符串全部打印出来,,因为对于字符串来说,他有专门打印字符串的%s,只需要找到该字符串首元素的地址,然后

以%s进行打印就可以打印出整个字符串的内容,,这是与整型和字符数组不一样的,这两者无法通过一次直接把所有的元素打印出来;

八、

int main()
{
 char *c[] = {"ENTER","NEW","POINT","FIRST"};
 char**cp[] = {c+3,c+2,c+1,c};
 char***cpp = cp;
 printf("%s\n", **++cpp);
 printf("%s\n", *--*++cpp+3);
 printf("%s\n", *cpp[-2]+3);
 printf("%s\n", cpp[-1][-1]+1);
 return 0; 
}

已知c这里没有&,也没有sizeof,,而且c是数组名,所以,c就代表数组首元素的地址,,,我们又知道,,c是放在char** cp[ ],这个数组里面的,,所以说,c的类型

就是char**类型,,现在c+1,,char**后面的*说明,是一个指针,所以,c+1就会跳过一个char*,,

而cp又是数组名,,没有&,没有sizeof,,所以这个数组名就代表数组首元素的地址,,即cp这个数组首元素的地址放在char三级指针变量cpp中,,指向的就是数组

cp首元素的地址,,前三句代码就可以把图画出来;

对于第一个printf,++的优先级高于*,,先进行++cpp,前置++,先++后使用,现在,cpp的类型是char***,,最后一个*代表cpp是一个指针变量,,++cpp就会跳过一

个char**类型的元素,,刚开始,cpp指向c+3,,现在++cpp,就会跳过一个char**类型,指向c+2,,,所以,++cpp后,指向的就是c+2的地址,,现在*++cpp,对

++cpp解引用,就拿到了c+2的内容,即就拿到了c+2这个元素,,而我们c+2的内容里面存放的就是''POINT''这个字符串的首元素即,字符P的地址的地址,即存放的就

是c[2]的地址,,,,现在,再对整体解引用,就拿到了c[2]的内容,就可以拿到字符p的地址,,或者看图,,我们第一次解引用拿到了c+2这个元素,即c+2这个元素

的内容,,这里面存放的又是第三个char*的地址,,所以,一次解引用我们就拿到了,第三个char*的地址,然后第二次解引用,就拿到了,第三个char*的内容,,第

三个char*的内容里面存放的又是字符p的地址,,所以,两次解引用就拿到了字符p的地址,

对于%s,打印字符串来说,如果一个字符串,''abcdef'',如果拿到了字符a的地址,以%s打印,,就会把整个字符串打印出来,,如果拿到了字符c的地址,以%s打

印,就会打印出来''cdef'',,所以对于,以%s打印,字符串来说,,,,拿到了谁的地址,就从那个字符开始打印,一直往后打印,,直到遇到\0,,,把\0前面,该地

址后面的,,所有的字符都打印出来;

所以,我们这里拿到了字符p的地址,,现在以%s打印,,就会把POINT这个字符串全部打印出来,

对于第二个printf,,++的优先级高于*,且++的优先级高于+,所以先算++cpp,,,我们cpp最初是指向c+3的地址,,即数组cp首元素的地址,在第一个printf中,,

已经有了++cpp,,所以,cpp自增了1,,现在cpp指向了c+2的地址,,然后,到第二个printf中,又有++cpp,,所以,,现在的cpp指向了c+1的地址,,*优先级高

于+,,然后,解引用,就拿到了c+1这个元素,就拿到了c+1这个元素的内容,,,,现在,又因为--的优先级高于+,,所以,要先对*++cpp这个整体先进行--,,因

为,*++cpp就相当于是c+1这个内容,而c+1的类型,,即,*++cpp的类型就是char**类型,,现在要对*++cpp这个整体进行--,char**类型后面的*代表是一个指针变

量,,就会跳过一个char*类型,,本来c+1指向了第二个char*,,现在,,往前跳过一个char*类型,,所以,c+1就指向了第一个char*的地址,又因*的优先级高于

+,, 然后再解引用,就拿到了第一个char*的内容,,第一个char*的内容里面是字符E的地址,,即,*--*++cpp就相当于是字符E的地址,,而字符E的地址类型又是

char*类型,,所以,*--*++cpp+3就会往后跳过3个char类型的元素,,所以,就找打了ER这个E的地址,,所以再以%s打印就会打印出来ER;

对于第三个printf,,[ ]的优先级高于*,,所以cpp[-2]先进行结合,,cpp[-2]===*(cpp-2),,因为cpp在第二个printf中已经指向了c+1的地址,而且,cpp的类型是

char***,现在,,cpp-2,,就是往前跳过2个char**类型,,所以,现在cpp-2指向了c+3的地址,,然后解引用,就拿到了c+3这个元素,然后,*的优先级高于+,

*cpp[-2]===*(*(cpp-2)),所以,现在,已经拿到了c+3这个元素,,c+3这个元素里面是第四个char*的地址,然后再解引用就拿到了第四个char*的内容,就是拿到

了F的地址,,现在,整体再+3,,就是,F的地址再加3,,又因F的地址类型是char*,所以+3就会往后跳过3个char类型,就找到了,S的地址,然后已知S的地址,

再以%s打印,,所以就把ST打印出来了,我们这里的,cpp-2,只是用了cpp的值,,让cpp-2得到一个其他的值,并没有把得到的这个值再次赋值给cpp,,所以cpp本

身并没有发生变化,,所以,第三个printf结束后,cpp仍指向c+1的地址,,并不会改变,,,我们的++cpp,,是先++,后使用,,即cpp自增1,这是可以使得cpp发

生改变的,,然后得到的这个值再拿去被使用,,++cpp和cpp++,就相当于是cpp=cpp+1,即cpp += 1,,和前面的cpp-2是不一样的,要注意区分;

对于第四个printf,,cpp[-1][-1]+1,,=== *(*(cpp-1)-1)+1,,从里往外看,,已知,cpp在第二个printf中已经指向了c+1的地址,,再第三个printf中,只是用到

了cpp的值,让cpp-1得到一个新的值,,但是这个新的值并没有在此赋给cpp,,所以,cpp的值没发生改变,,所以,cpp还是指向c+1的地址,,,cpp的类型是

char***,所以,cpp-1就会往前跳过一个char**类型,,所以,cpp-1就指向了c+2的地址,,然后解引用,就拿到了c+2这个元素,c+2这个元素的内容就是第三个char*

的地址,,然后c+2这个元素的类型是char**,,所以,*(cpp-1)就相当于是c+2,,*(cpp-1)-1就会往前跳过一个char*,,

所以现在,*(cpp-1)-1即指向了第二个char*的地址,,然后解引用,就拿到了第二个char*的内容,,即字符N的地址,,而字符N地址的类型又是char*,,,所以,

现在整体再+1,,而字符N的地址类型又是char*,,所以就会往后跳过一个char类型,,找到了E的地址,,然后以%s打印出来,EW;

这里的,c是一级字符指针数组,cp是二级字符指针数组,,cpp是一个三级字符指针;

到此为止,指针进阶算是结束了,,如果对您有帮助,记得点赞收藏~~

  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

脱缰的野驴、

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

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

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

打赏作者

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

抵扣说明:

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

余额充值