Error[8]: Undefined offset: 120, File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 121
File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 473, decode(

上一篇:从0开始学c语言-29-数据储存练习、筑基期练习、两步翻转_阿秋的阿秋不是阿秋的博客-CSDN博客

应有的基础:

从0开始学c语言-28-qsort函数、 数组和指针参数、函数指针数组(转移表)、回调函数_阿秋的阿秋不是阿秋的博客-CSDN博客

从0开始学c语言-27-字符指针,指针数组和数组指针_阿秋的阿秋不是阿秋的博客-CSDN博客

目录

练习1:int arr[ ]

 练习2:char arr[ ] zifu

 对比

 练习3:char arr[ ]

 对比\0

 练习4:char*p

对比 

 练习5:二维数组

 练习6:判断程序结果

练习7:第一眼没看出来的考点(重点看)

***指针和地址的新理解***

分析题

练习8:%x?int+1?(又被击败)

对于非指针+1的步长理解(地址神奇~)

练习9:有点陷阱

练习10:二维数组指针运用、指针运算、%p打印整型

练习11:二维数组

练习12:char*a[ ]

练习13:char***之终极练习


练习1:int arr[ ]

应有的知识:

数组名的意义: 1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。 2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。 3. 除此之外所有的数组名都表示首元素的地址。
//一维数组
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a+0));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(a[1]));
printf("%d\n",sizeof(&a));
printf("%d\n",sizeof(*&a));
printf("%d\n",sizeof(&a+1));
printf("%d\n",sizeof(&a[0]));
printf("%d\n",sizeof(&a[0]+1));

请思考每个printf函数输出的结果是多少。

开始讲

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));

a单纯的数组名实际上就是数组首元素的地址,那么地址的大小在32位系统上是4byte,在64位系统上是8byte大小。但是呢,sizeof当中的数组名则代表整个数组,这一点要特别注意,那么a数组的大小就是4个int的大小,也就是16byte。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a+0));

实际上地址就是指针,那么a+0就代表a这个指针向后挪动了0步。且a本身指向数组首元素,+0表示一步没有挪动。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(*a));

我们知道a是指针,*a就是对指针进行解引用,a指向数组首元素,那么*a就是在访问数组首元素,而数组首元素的类型是int,int的大小是4byte。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a+1));

a+0和a+1都是指针,只不过一个指向首元素,一个指向下标为1的元素。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a[1]));

a[1]代表数组下标为1的元素,而这个元素的类型是int,那么就是4byte。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(&a));

我们早在前面说过,

1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。 2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
那么现在是sizeof和&数组名组合到一起,这时候,sizeof当中的&数组名,则是计算的 【指向整个数组的指针】的大小。
printf("%d\n",sizeof(*&a));

现在在&a前面加上了*, 而*&a=a,a又在sizeof当中,那就代表我们访问了整个数组,应该是16byte。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(&a+1));

&a代表指向整个数组的指针,那么+1则会让指针向后挪动一步,这一步的大小是整个数组的大小,我们计算的还是 指针的大小。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(&a[0]));

&a[0]是什么意思?

如果讲结合性的话,我还不太熟练,只能放图监视给你看

 也就是说&a[0]=&(a[0])

意思是取出数组第一个元素的地址,本质上是个指针。
printf("%d\n",sizeof(&a[0]+1));

那么同样的,&a[0]+1就代表指向第一个元素的指针向后走了一步,而这一步的大小是一个int,也就是指向了下标为1的元素。

现在看看结果,是否符合我们的推理。

 练习2:char arr[ ] zifu
char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));
	printf("%d\n", sizeof(arr + 0));
	printf("%d\n", sizeof(*arr));
	printf("%d\n", sizeof(arr[1]));
	printf("%d\n", sizeof(&arr));
	printf("%d\n", sizeof(&arr + 1));
	printf("%d\n", sizeof(&arr[0] + 1));

实际上,这题和上面那道题几乎一模一样。我就把解释写在代码里了哈。

char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));
//整个数组的大小,6
	printf("%d\n", sizeof(arr + 0));
//指向下标1的指针,4/8 ,8代表64位系统的结果
	printf("%d\n", sizeof(*arr));
//访问数组第一个元素,1
	printf("%d\n", sizeof(arr[1]));
//访问数组第一个元素,1
	printf("%d\n", sizeof(&arr));
//指向整个数组的指针,4/8
	printf("%d\n", sizeof(&arr + 1));
//指向整个数组的指针向后挪动一步,4/8
	printf("%d\n", sizeof(&arr[0] + 1));
//指向数组第一个元素的指针向后挪动一步,4/8

 对比
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));

实际上大体也是一样的,只不过我们换成了strlen,那就要特别注意\0。

char arr[] = { 'a','b','c','d','e','f' };
    printf("%d\n", strlen(arr));
//没有char arr[] = "abcdef";
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr+0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr+1));
printf("%d\n", sizeof(&arr[0]+1));,随机值
    printf("%d\n", strlen(arr+0));
//没有char arr[] = "abcdef";
    printf("%d\n", sizeof(arr));
//sizeof中的数组名代表整个数组,7(记得有隐藏的char arr[] = "abcdef";
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));元素)
    printf("%d\n", sizeof(arr+0));
//arr+0代表指针,指向第一个元素,4/8 (8是64位系统的结果)
    printf("%d\n", sizeof(*arr));
//代表字符串的第一个字符,类型char,1
    printf("%d\n", sizeof(arr[1]));
//代表字符串的第一个字符,类型char,1
    printf("%d\n", sizeof(&arr));
//指向整个char数组的指针,4/8
    printf("%d\n", sizeof(&arr+1));
//指向整个char数组的指针向后挪一步,4/8
    printf("%d\n", sizeof(&arr[0]+1));
//指向字符串第一个字符的指针向后挪一步,4/8,随机值
    printf("%d\n", strlen(*arr));
//*arr不是指针,不能计算
    printf("%d\n", strlen(arr[1]));
//arr[1]不是指针,不能计算
    printf("%d\n", strlen(&arr));
//没有char arr[] = "abcdef";
    printf("%d\n", strlen(arr));
//6
    printf("%d\n", strlen(arr+0));
//6
    printf("%d\n", strlen(*arr));
//不能计算,strlen要的是const char*指针类型的数据
    printf("%d\n", strlen(arr[1]));
//不能计算,strlen要的是const char*指针类型的数据
//虽然后面这两个语句不是const char*类型,但是可以把地址传过去
    printf("%d\n", strlen(&arr));
//6
    printf("%d\n", strlen(&arr+1));
//指向数组的指针向后挪动一步
//这一步的大小是一个数组的大小,打印结果随机值
    printf("%d\n", strlen(&arr[0]+1));
//指向数组中首元素的指针向后挪一步
//这步大小是一个char的大小,打印结果是5,随机值
    printf("%d\n", strlen(&arr+1));
//没有char *p = "abcdef";
printf("%d\n", sizeof(p));
printf("%d\n", sizeof(p+1));
printf("%d\n", sizeof(*p));
printf("%d\n", sizeof(p[0]));
printf("%d\n", sizeof(&p));
printf("%d\n", sizeof(&p+1));
printf("%d\n", sizeof(&p[0]+1));,随机值-6  因为是指向整个数组的指针向后挪一步
    printf("%d\n", strlen(&arr[0]+1));
//没有char *p = "abcdef";
    printf("%d\n", sizeof(p));
//p是个指针,4/8 (8是64位系统的结果)
    printf("%d\n", sizeof(p+1));
//p+1指向下一个字符c,4/8
    printf("%d\n", sizeof(*p));
//*p代表首字符字符a,1
    printf("%d\n", sizeof(p[0]));
//代表首字符字符a,1
    printf("%d\n", sizeof(&p));
//&p代表指针p的地址,4/8
    printf("%d\n", sizeof(&p+1));
//指向指针p的指针向后挪动一步,这一步大小是一个char*,4/8
    printf("%d\n", sizeof(&p[0]+1));
//指向首字符的指针向后挪动一步,这一步的大小是一个char,4/8,随机值-1 因为是指向一个元素的指针向后挪一步

很多警告,主要看类型不同的警告,正如我们所说,*arr和arr[0]是不能计算的,

在注释掉后,计算的结果就像我们注释里说的。

 练习3:char arr[ ]
char *p = "abcdef";
printf("%d\n", strlen(p));
printf("%d\n", strlen(p+1));
printf("%d\n", strlen(*p));
printf("%d\n", strlen(p[0]));
printf("%d\n", strlen(&p));
printf("%d\n", strlen(&p+1));
printf("%d\n", strlen(&p[0]+1));

要知道的是,储存字符串当中的字符数组名是第一个字符的地址。

char *p = "abcdef";
    printf("%d\n", strlen(p));
//6
    printf("%d\n", strlen(p+1));
//5
    printf("%d\n", strlen(*p));
    printf("%d\n", strlen(p[0]));
//上面的*p和p[0]是char类型,不能计算
    printf("%d\n", strlen(&p));
//p此时是个指向首元素的指针,而&p代表指向p的指针
//那么&p中保存的是p的地址,int a[3][4] = {0};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a[0][0]));
printf("%d\n",sizeof(a[0]));
printf("%d\n",sizeof(a[0]+1));
printf("%d\n",sizeof(*(a[0]+1)));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(*(a+1)));
printf("%d\n",sizeof(&a[0]+1));
printf("%d\n",sizeof(*(&a[0]+1)));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a[3]));位置不确定,计算值会是个随机值
    printf("%d\n", strlen(&p+1));
//&p是个指向p的指针,类型char**,向后挪动一步的大小是char*
//char*是个指针,指针的大小是4/8
//所以结果是随机值-4/8 (8是64位系统的)
    printf("%d\n", strlen(&p[0]+1));
//5

 对比\0
int a[3][4] = {0};
    printf("%d\n",sizeof(a));
//sizeof中的a代表整个数组,12*4=48
    printf("%d\n",sizeof(a[0][0]));
//第一行第一个元素,4

注意strlen是找\0来结束运算的,而字符串后会有一个隐藏的\0

printf("%d\n",sizeof(a[0]));
//实际上a[0]是第一行一维数组的数组名,那么
//sizeof中的数组名代表整个数组
//而数组名a[0]其实就是个指针,指向4个int
//二维数组中第一行的大小,第一行4个元素
//结果是4*4=16

 练习4:char*p
printf("%d\n",sizeof(a[0]+1));
//a[0]的类型是int[4],作为指针就是int*,指向int
//向后加一的步长是int,指向第一行下标为1的元素
//输出结果是4/8

第一句的意思是,char*指针p指向后面字符串首元素a的地址。

printf("%d\n",sizeof(*(a[0]+1)));
//a[0]+1代表指向 第一行下标为1的元素 的指针
//解引用后代表访问这个元素
//元素类型int,那么结果为4

对比 
printf("%d\n",sizeof(a+1));
//a的类型是int[3][4]
//作为指针的话是一个一维数组int[4]*
//+1的步长是4个int,那么a+1就代表第二行的地址
//结果是4/8
    printf("%d\n",sizeof(*(a+1)));
//*(a+1)=a[1],是第二行的一维数组名
//sizeof中的数组名代表整个数组的大小
//此时的a[1]是第二行,有4个元素
//那就是4个int,16byte

依旧注意p是个指向a的指针,strlen主要看\0的位置。

printf("%d\n",sizeof(&a[0]+1));
//a[0]代表第一行的数组名
//&a[0]就代表指向第一行的指针
//此时的+1步长是 一行数组 的大小
//指针指向第二行的一维数组
//结果4/8
    printf("%d\n",sizeof(*(&a[0]+1)));
//&a[0]+1代表 指向第二行一维数组的指针
//对指针解引用就是a[1]
//结果4*4=16

 练习5:二维数组
printf("%d\n",sizeof(*a));
//a的类型是int[3][4],作为指针是int[4]*
//解引用后是int[4]
//那么*a就代表第一行的数组名
//实际上*a=*(a+0)=a[0]
//那么结果是4*4=16
    printf("%d\n",sizeof(a[3]));
//二维数组一共三行,最大是a[2]
//sizeof中的a[3]结果是这个表达式的类型大小
//所以不涉及越界访问的问题
//直接就是一行元素的大小,16

二维数组的话要明白,二维数组的数组名在作为指针的情况下是个一维数组指针。

int main()
{
    int a[5] = { 1, 2, 3, 4, 5 };
    int *ptr = (int *)(&a + 1);
    printf( "%d,%d", *(a + 1), *(ptr - 1));
    return 0; }
//由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
 int Num;
 char *pcName;
 short sDate;
 char cha[2];
 short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
 printf("%p\n", p + 0x1);
 printf("%p\n", (unsigned long)p + 0x1);
 printf("%p\n", (unsigned int*)p + 0x1);
 return 0; }
还不完全一样
我们得看指针类型,才能确定相邻的指针会差多少字节。
非要说有的话,也只有在按照不同数据进行分配空间后才算是有了类型。
而地址在有了类型后,也就是指针了。
以字节的方式来理解地址和指针

 练习6:判断程序结果
//假设p 的值为0x100000。 如下表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
     printf("%p\n", p + 0x1);
//p+1的步长是20字节 - 十六进制是14
//0x100014
     printf("%p\n", (unsigned long)p + 0x1);
//注意是把p这个结构体指针转换成一个整型了
//所以是加了十六进制的数字1
//结果是0x100001
     printf("%p\n", (unsigned int*)p + 0x1);
//p+1的步长是4或者8字节(32位/64位系统)
//0x100004

首先看*(a+1),a+1代表下标为1的地址,*(a+1)=a[1],结果是2

*(ptr - 1),先分析ptr接收了谁的地址

&a+1 代表 &a是整个数组的地址,+1则是指向a后面一个数组大小的地址

int*强制类型转换后,只能访问一个int大小的地址并存到ptr中,

此时ptr指向a[4]这个元素后面的地址,ptr是int*指针

那么ptr-1的步长是一个int,ptr-1指向a[4],解引用后就是5

output:2,5

练习7:第一眼没看出来的考点(重点看)
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; }

说实话,这个题我做过一遍,第二遍还是没看出来在干嘛

然后我思考了一下原因,就在于我被0x1迷惑了。我一看到0x1完全忘了指针p的作用,只想着地址加1是怎么加啊?

我一直在想地址加1难道不是纯粹的加1吗?这题为啥要这么写?

还真不是!!!

***指针和地址的新理解***

因为你要知道的是其实指针就是地址,地址就是指针

但是还有一个问题,那为什么普通的整型+1就是数值上的+1而不是字节上的+1呢?,因为如果单纯的说地址,那地址是一个储存单元一个地址,相邻的地址会相差1个字节,但是相邻的指针会相差一个字节吗?

那还真不是,地址之间相邻差1byte,所以+1 的步长就是1byte

那么话说回来了,地址会有类型吗? 那还真没有,如果 (int)a (int)a+1
但是并没有看到说对地址分类的说法,所以我建议是 ,也就是说,无论是地址还是字节,在分析+1步长的时候,得 看看它指向的类型占了几个字节来判断。
我觉得这个理解超级对!!以后的我,常回来看看,有新的理解一定要及时改动! 分析题 现在来分析题目,其实题目考察的就是指针类型对于+1步长的影响。
 
 练习8:%x?int+1?(又被击败) 
 
 
 

先分析ptr1指针接收到的地址是谁

&a是整个数组的大小,+1后指向a数组后面 一个数组大小 的空间

int*强制类型转换后就只能访问一个int大小的空间,把这个空间(也就是a[3]后面的int空间)存到了ptr1中

对于非指针+1的步长理解(地址神奇~)

接着是ptr2,首先a是指向数组首元素的,但是被强制转换为了int类型的数据,那么(int)a+1的步长会是多少呢?

int*+1会跳过4个字节,因为指针指向的int占4byte

char*+1跳过1个字节,因为指针指向的char占1byte

类似的,

我们的int+1是多大呢?难道是单纯的1+1=2吗?

我的思考过程是这样

        首先a是数组名,指向首元素的地址,在int强制转换后,是把一个地址强制转换为了int类型。按理说,int+1应该是按照一个int大小来向后挪动的啊!但是这样的话,int*指针又该怎么解释步长是一个int大小呢?

        关于这个问题,其实上个练习也涉及到了。需要知道的是我们在转换为int的时候,是把地址转换为了int类型的数据,也就是说此时的地址不是指向某个数据的指针,而是单纯的数字,其实就是给地址编号加了个数字1,而。

那这时候我们知道了(int)a+1就是a内存中第二个字节的地址,此时把这个地址转换为int*指针,而一个int*指针可以访问4个字节。我们按照小端储存来看看此时的ptr储存的是哪些。

00000002那么 p 指向的就是 a 数组中第一行的前四个元素。a作为指针使用的时候就是int[5]*指针*(a+i)=a[ i ]
010000
00(我加了#,因为没有0x看着别扭)int main() {    int a[3][2] = { (0, 1), (2, 3), (4, 5) };    int *p;    p = a[0];    printf( "%d", p[0]); return 0; }int a[3][2] = { (0, 1), (2, 3), (4, 5) }; int a[3][2] = { 1, 3, 5 };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; }

上面标红的就是ptr指向的内存单元。

那么打印的结果

ptr[-1]是指向ptr指针前面一个int空间的数据,这个空间的数据是a[3]。

按照小端储存的内存中是(低地址)04 00 00 00(高地址)

因为我们把数据存放到内存中按照小端储存,那就是数据的低位放到低地址,高位放到高地址,所以打印出来就是(高位)00 00 00 04 (低位)(省略前面的0)

*ptr则是解引用ptr指针,去通过这个地址访问,访问到的是(低地址)00 00 00 02(高地址)

因为我们把数据存放到内存中按照小端储存,那就是数据的低位放到低地址,高位放到高地址,打印出来的就是(高位)02 00 00 00(低位)(省略前面的0)

看结果a[4]的意思就是a向后挪动了4个步长,每个步长是int[5],那么此时的a[4]已经跳过了4*5=20个int元素

 地址转换为int+1,就是单纯的数字+1啦,实际上就是跳过了一个byte的地址。

练习9:有点陷阱
p[4]就代表p指针向后挪动了4个步长,每个步长是int[4],一共跳过了4*4=16个int元素

这题其实挺搞笑的,要是看走眼就无了

你要注意的是后面是逗号表达式,所以第一行的语句实际上就是

总结:二维数组

a[0]代表二维数组第一行,也就是1和3两个元素。

我们的int*p指向指向了a[0]这个一维数组,也就是说我们指向了第一行int[2]大小的首元素地址,因为p是int*指针,便一次指向一个int大小的空间,也就是指向首元素1。

而打印中的p[0]实际上就等价于*(p+0),也就是首元素1。

练习10:二维数组指针运用、指针运算、%p打印整型
p [4][2]=* ( * ( p + 4 ) + 2 );   

a的类型是int[5][5]二维数组,我们的p是int[4]*类型的指针,指向int[4]。

此时我们把p=a,而 a 没有被&也没有在sizeof当中,所以是数组首元素的地址,也就是第一行

因为地址无所谓正负,所以%p认为内存中存的就是一个地址,直接按照内存中的补码输出。

接着分析&p[4][2]和&a[4][2]什么意思

        首先我们知道p是指向a数组中第一行前四个元素的指针,是一个int[4]*的指针。

        a我们说是int[5][5]类型,而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; },那么此时a+1的步长就会是一个int[5]的大小,而我们又知道ptr1指向的就是aa数组最后一个元素后面的一个int大小的空间,代表a这个指针向后挪动了 i 步并访问这个空间的元素。那么int main() { char *a[] = {"work","at","alibaba"}; char**pa = a; pa++; printf("%s\n", *pa); return 0; }。此时的a[4]是一个int[5]类型的数组,把a[4]看做这个int[5]的数组名,那么a[4]数组名在作为指针使用的时候就是int*指针,那么a[4][2]就代表访问了a[4]数组中下标为2的元素。

        同样的道理,p是一个int[4]*的指针,我们又知道p的地址是a数组首元素的地址,指向a数组第一行的前四个元素,那么它+1的步长是int[4],"work" 字符串  是一堆char类型数据的集合,也就是char[5]数组,此时的p[4]就是一个int[4]类型的数组名,而p[4]数组名在作为指针使用的时候就是int*指针,p[4][2]就代表访问了p[4]数组中下标为2的元素。

“work”字符串就相当于char[5]的数组名,

数组名在运用的时候会变为指针,也就是会从char[5]数组变为char*指针(注:sizeof和&中的数组名除外)

我画个图,配合来看。

此时p[4][2]和a[4][2]的地址相4个int,也就是16个byte。

要知道 &p[4][2] - &a[4][2]是指针之间的元素个数,又因为p的地址比a的地址低,那么%d的形式打印出来就是-4,而%p的话则需要把-4的补码写出来,按照16进制数字进行输出。

那么为什么%p就不用像%d一样进行原码转换输出,而是直接使用补码呢?

数组名“work”作为指针的时候是指向首元素的,也就是指向w这个字符

这里我们演示一下

要达到,一眼能看出来代码在说什么的水平,这题就算OK了。

放一下结果

练习11:二维数组
当我们在char*[ ]数组中存放“work”字符串的时候,是存放了字符w的地址。

这题比较简单。

首先&aa是取出来了整个二维数组大小的地址,向后+1的步长是一个二维数组的大小,转换为int*指针便只能访问一个int,那么如图。

*(ptr-1)就是指ptr指针向前挪了一个int大小,正好指向数组最后一个元素,然后访问住户,那么结果就是10。

同样的道理,

aa是int[2][5]数组,在作为指针运用的时候是一个int[5]*指针,指向第一行的一维int[5]数组,+1的步长就是指向第二行的一维数组int[5],加上*解引用代表访问这个数组,那么也就相当于aa[1]。

实际上也可直接利用*(a+i)=a[ i ],上面只是详细解释了一下。

aa[1]的类型是int[5],此时被强制转化为int*指针,那便只能指向一个int大小的空间,也就是指向a[1][0],那么ptr2就是指向了a[1][0]。

*(ptr2-1)就是向前挪了一个int大小,指向a[0][4]元素,*代表访问元素,结果就是5。

如图。 

练习12:char*a[ ]
 char**pa = a;

        首先我们要理解char*a[ ]初始化意味着什么,要知道数组中存放的是char*类型的数据,而我们初始化的时候放的是字符串,那么字符串又意味着什么?

        可以看到  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; }。那么 char *c[] = {"ENTER","NEW","POINT","FIRST"}; char**cp[] = {c+3,c+2,c+1,c};而我们又知道,char**[ ]则代表我们存放的数据是char**这个二级指针数据。所以实际上我们存放的就是char*类型的数据,还需要知道的是数组名c存放了首元素char*的地址。

        也就是说,实际上c,c+1,c+2,c+3就是char**类型的数据

c,c+1,c+2,c+3便分别指向c数组中下标为0,1,2,3,4,的元素

而这些元素的类型都是char*类型,

上面这个语句的意思是,现在我们用pa指针指向a数组中首元素的地址,指向的数据类型是char*。

也就是说指向第一个char*。如图。

         pa++是指向第二个char*的指针,现在对指针进行*解引用,也就是访问这个char*元素,而我们在打印的时候需要传递的就是char*数据,这个char*指向字符串“at”当中'a'的地址,所以打印的结果是at。

练习13:char***之终极练习
c,c+1,c+2,c+3这四个char**元素,分别存放了下标为0,1,2,3,4,的char*元素的地址。(指向谁,存放谁的地址)

 首先梳理第一句的意思,我们在上面已经分析过char*[ ]当中存放字符串意味着什么,这里便只放图了。

 存放了四个字符串首元素的地址。

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);,那么我们又知道c是char*[4]数组,是一个数组名,而数组名在运用的时候就会变为指向首元素的指针,也就是从char*[4]类型变为char**指针,代表指向char*数据的指针,也就是 *++cpp=c+2。所以 printf("%s\n", **++cpp); printf("%s\n", *--*++cpp+3); printf("%s\n", *cpp[-2]+3); printf("%s\n", cpp[-1][-1]+1);,又因为c是指向数组首元素的,那么c+1,c+2,c+3便是指针c向后移动的步数,c是一个char**指针,移动的步长是char*,那么此时的 第二句的cpp是上一句的++cpp,是指向c+2的。总体过程是:*--*++cpp + 3 = *--(c+1)+ 3 = *c+3 = "ENTER" + 3 = "ER"也就是 "ENTER"(字符串数组名,实际上是首元素'E'的地址,类型char*)

 

接着是第三句。

 printf("%s\n", **++cpp);
 printf("%s\n", *--*++cpp+3);
 printf("%s\n", *cpp[-2]+3);
 printf("%s\n", cpp[-1][-1]+1);
 char***是指向char**数据的指针,此时的cp是指向首元素c+3这个char**的char***指针,把cp的地址交给cpp,也就代表cpp和cp一样,是指向首元素c+3这个char**的char***指针,也就是存放了c+3这个char**的地址。
*cpp[-2]+3 = *(*(cpp-2))+3 = *(c+3)+3 = "FIRST" + 3 = "ST"

先分析第一句,++cpp代表cpp向后挪动了一步,这一步的大小是一个char**。++cpp的类型是char***,对++cpp进行解引用,*++cpp就是char**类型,也就是在访问++cpp所指向的对象,此时

++cpp指向c+2,那么"FIRST"(字符串数组名,实际上是首元素'F'的地址,类型char*)

再进行解引用的话,就 相当于对c+2进行解引用,c+2指向的对象是  "POINT" 的 首字符 'P' 地址,对c+2进行解引用代表访问  c+2所指向的对象,也就是访问"POINT" 的 首字符 'P' 地址,此时把这个地址用%s的形式打印, 那么便是POINT。 如图。

 现在分析第二句。

 printf("%s\n", **++cpp);
 printf("%s\n", *--*++cpp+3);
 printf("%s\n", *cpp[-2]+3);
 printf("%s\n", cpp[-1][-1]+1);

 我们需要知道的是前置后置++和--都会对数据本身造成影响,也就是说cpp[-1][-1]+1 = *(*(cpp-1)-1)+ 1 = *((c+2)-1)+1 = *(c+1)+1 = "NEW"+1=其次,我们要先运算*--*++cpp+3中的++cpp。那么++cpp指向c+1,解引用的话就是访问++cpp指向的对象,++cpp指向c+1,那么*++cpp=c+1,然后我们再对c+1进行前置--,那么c+1就会变为c。再对c进行解引用,就是在访问c所指向的对象,c指向ENTER字符串中E的地址,再对这个地址+3就意味着,从字符E向后走了3步,每步大小是一个字符char,那么打印结果是ER。

"EW" "NEW"(字符串数组名,实际上是首元素'N'的地址,类型char*)

 分析第三句

直接写出整体运算过程
此时的cpp指向cp数组中第三个元素,(不再细说,道理想通) [+++] [+++] 分析最后一句
[+++]

因为上一句并不是使用的前置后置++或者--,所以cpp还是指向cp数组中第三个元素。

[+++][+++]

[+++]

 这题达到能[+++]的水平就可以了。

练习到此,后续还会有练习,不过这算是比较深入的练习了,如果我没解释清楚,可以看我之前对于指针文章的解释,或者直接私信我,很乐意解答,可惜没人看哈哈哈

)
File: /www/wwwroot/outofmemory.cn/tmp/route_read.php, Line: 126, InsideLink()
File: /www/wwwroot/outofmemory.cn/tmp/index.inc.php, Line: 166, include(/www/wwwroot/outofmemory.cn/tmp/route_read.php)
File: /www/wwwroot/outofmemory.cn/index.php, Line: 30, include(/www/wwwroot/outofmemory.cn/tmp/index.inc.php)
Error[8]: Undefined offset: 121, File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 121
File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 473, decode(

上一篇:从0开始学c语言-29-数据储存练习、筑基期练习、两步翻转_阿秋的阿秋不是阿秋的博客-CSDN博客

应有的基础:

从0开始学c语言-28-qsort函数、 数组和指针参数、函数指针数组(转移表)、回调函数_阿秋的阿秋不是阿秋的博客-CSDN博客

从0开始学c语言-27-字符指针,指针数组和数组指针_阿秋的阿秋不是阿秋的博客-CSDN博客

目录

练习1:int arr[ ]

 练习2:char arr[ ] zifu

 对比

 练习3:char arr[ ]

 对比\0

 练习4:char*p

对比 

 练习5:二维数组

 练习6:判断程序结果

练习7:第一眼没看出来的考点(重点看)

***指针和地址的新理解***

分析题

练习8:%x?int+1?(又被击败)

对于非指针+1的步长理解(地址神奇~)

练习9:有点陷阱

练习10:二维数组指针运用、指针运算、%p打印整型

练习11:二维数组

练习12:char*a[ ]

练习13:char***之终极练习


练习1:int arr[ ]

应有的知识:

数组名的意义: 1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。 2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。 3. 除此之外所有的数组名都表示首元素的地址。
//一维数组
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a+0));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(a[1]));
printf("%d\n",sizeof(&a));
printf("%d\n",sizeof(*&a));
printf("%d\n",sizeof(&a+1));
printf("%d\n",sizeof(&a[0]));
printf("%d\n",sizeof(&a[0]+1));

请思考每个printf函数输出的结果是多少。

开始讲

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));

a单纯的数组名实际上就是数组首元素的地址,那么地址的大小在32位系统上是4byte,在64位系统上是8byte大小。但是呢,sizeof当中的数组名则代表整个数组,这一点要特别注意,那么a数组的大小就是4个int的大小,也就是16byte。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a+0));

实际上地址就是指针,那么a+0就代表a这个指针向后挪动了0步。且a本身指向数组首元素,+0表示一步没有挪动。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(*a));

我们知道a是指针,*a就是对指针进行解引用,a指向数组首元素,那么*a就是在访问数组首元素,而数组首元素的类型是int,int的大小是4byte。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a+1));

a+0和a+1都是指针,只不过一个指向首元素,一个指向下标为1的元素。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a[1]));

a[1]代表数组下标为1的元素,而这个元素的类型是int,那么就是4byte。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(&a));

我们早在前面说过,

1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。 2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
那么现在是sizeof和&数组名组合到一起,这时候,sizeof当中的&数组名,则是计算的 【指向整个数组的指针】的大小。
printf("%d\n",sizeof(*&a));

现在在&a前面加上了*, 而*&a=a,a又在sizeof当中,那就代表我们访问了整个数组,应该是16byte。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(&a+1));

&a代表指向整个数组的指针,那么+1则会让指针向后挪动一步,这一步的大小是整个数组的大小,我们计算的还是 指针的大小。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(&a[0]));

&a[0]是什么意思?

如果讲结合性的话,我还不太熟练,只能放图监视给你看

 也就是说&a[0]=&(a[0])

意思是取出数组第一个元素的地址,本质上是个指针。
printf("%d\n",sizeof(&a[0]+1));

那么同样的,&a[0]+1就代表指向第一个元素的指针向后走了一步,而这一步的大小是一个int,也就是指向了下标为1的元素。

现在看看结果,是否符合我们的推理。

 练习2:char arr[ ] zifu
char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));
	printf("%d\n", sizeof(arr + 0));
	printf("%d\n", sizeof(*arr));
	printf("%d\n", sizeof(arr[1]));
	printf("%d\n", sizeof(&arr));
	printf("%d\n", sizeof(&arr + 1));
	printf("%d\n", sizeof(&arr[0] + 1));

实际上,这题和上面那道题几乎一模一样。我就把解释写在代码里了哈。

char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));
//整个数组的大小,6
	printf("%d\n", sizeof(arr + 0));
//指向下标1的指针,4/8 ,8代表64位系统的结果
	printf("%d\n", sizeof(*arr));
//访问数组第一个元素,1
	printf("%d\n", sizeof(arr[1]));
//访问数组第一个元素,1
	printf("%d\n", sizeof(&arr));
//指向整个数组的指针,4/8
	printf("%d\n", sizeof(&arr + 1));
//指向整个数组的指针向后挪动一步,4/8
	printf("%d\n", sizeof(&arr[0] + 1));
//指向数组第一个元素的指针向后挪动一步,4/8

 对比
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));

实际上大体也是一样的,只不过我们换成了strlen,那就要特别注意\0。

char arr[] = { 'a','b','c','d','e','f' };
    printf("%d\n", strlen(arr));
//没有char arr[] = "abcdef";
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr+0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr+1));
printf("%d\n", sizeof(&arr[0]+1));,随机值
    printf("%d\n", strlen(arr+0));
//没有char arr[] = "abcdef";
    printf("%d\n", sizeof(arr));
//sizeof中的数组名代表整个数组,7(记得有隐藏的char arr[] = "abcdef";
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));元素)
    printf("%d\n", sizeof(arr+0));
//arr+0代表指针,指向第一个元素,4/8 (8是64位系统的结果)
    printf("%d\n", sizeof(*arr));
//代表字符串的第一个字符,类型char,1
    printf("%d\n", sizeof(arr[1]));
//代表字符串的第一个字符,类型char,1
    printf("%d\n", sizeof(&arr));
//指向整个char数组的指针,4/8
    printf("%d\n", sizeof(&arr+1));
//指向整个char数组的指针向后挪一步,4/8
    printf("%d\n", sizeof(&arr[0]+1));
//指向字符串第一个字符的指针向后挪一步,4/8,随机值
    printf("%d\n", strlen(*arr));
//*arr不是指针,不能计算
    printf("%d\n", strlen(arr[1]));
//arr[1]不是指针,不能计算
    printf("%d\n", strlen(&arr));
//没有char arr[] = "abcdef";
    printf("%d\n", strlen(arr));
//6
    printf("%d\n", strlen(arr+0));
//6
    printf("%d\n", strlen(*arr));
//不能计算,strlen要的是const char*指针类型的数据
    printf("%d\n", strlen(arr[1]));
//不能计算,strlen要的是const char*指针类型的数据
//虽然后面这两个语句不是const char*类型,但是可以把地址传过去
    printf("%d\n", strlen(&arr));
//6
    printf("%d\n", strlen(&arr+1));
//指向数组的指针向后挪动一步
//这一步的大小是一个数组的大小,打印结果随机值
    printf("%d\n", strlen(&arr[0]+1));
//指向数组中首元素的指针向后挪一步
//这步大小是一个char的大小,打印结果是5,随机值
    printf("%d\n", strlen(&arr+1));
//没有char *p = "abcdef";
printf("%d\n", sizeof(p));
printf("%d\n", sizeof(p+1));
printf("%d\n", sizeof(*p));
printf("%d\n", sizeof(p[0]));
printf("%d\n", sizeof(&p));
printf("%d\n", sizeof(&p+1));
printf("%d\n", sizeof(&p[0]+1));,随机值-6  因为是指向整个数组的指针向后挪一步
    printf("%d\n", strlen(&arr[0]+1));
//没有char *p = "abcdef";
    printf("%d\n", sizeof(p));
//p是个指针,4/8 (8是64位系统的结果)
    printf("%d\n", sizeof(p+1));
//p+1指向下一个字符c,4/8
    printf("%d\n", sizeof(*p));
//*p代表首字符字符a,1
    printf("%d\n", sizeof(p[0]));
//代表首字符字符a,1
    printf("%d\n", sizeof(&p));
//&p代表指针p的地址,4/8
    printf("%d\n", sizeof(&p+1));
//指向指针p的指针向后挪动一步,这一步大小是一个char*,4/8
    printf("%d\n", sizeof(&p[0]+1));
//指向首字符的指针向后挪动一步,这一步的大小是一个char,4/8,随机值-1 因为是指向一个元素的指针向后挪一步

很多警告,主要看类型不同的警告,正如我们所说,*arr和arr[0]是不能计算的,

在注释掉后,计算的结果就像我们注释里说的。

 练习3:char arr[ ]
char *p = "abcdef";
printf("%d\n", strlen(p));
printf("%d\n", strlen(p+1));
printf("%d\n", strlen(*p));
printf("%d\n", strlen(p[0]));
printf("%d\n", strlen(&p));
printf("%d\n", strlen(&p+1));
printf("%d\n", strlen(&p[0]+1));

要知道的是,储存字符串当中的字符数组名是第一个字符的地址。

char *p = "abcdef";
    printf("%d\n", strlen(p));
//6
    printf("%d\n", strlen(p+1));
//5
    printf("%d\n", strlen(*p));
    printf("%d\n", strlen(p[0]));
//上面的*p和p[0]是char类型,不能计算
    printf("%d\n", strlen(&p));
//p此时是个指向首元素的指针,而&p代表指向p的指针
//那么&p中保存的是p的地址,int a[3][4] = {0};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a[0][0]));
printf("%d\n",sizeof(a[0]));
printf("%d\n",sizeof(a[0]+1));
printf("%d\n",sizeof(*(a[0]+1)));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(*(a+1)));
printf("%d\n",sizeof(&a[0]+1));
printf("%d\n",sizeof(*(&a[0]+1)));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a[3]));位置不确定,计算值会是个随机值
    printf("%d\n", strlen(&p+1));
//&p是个指向p的指针,类型char**,向后挪动一步的大小是char*
//char*是个指针,指针的大小是4/8
//所以结果是随机值-4/8 (8是64位系统的)
    printf("%d\n", strlen(&p[0]+1));
//5

 对比\0
int a[3][4] = {0};
    printf("%d\n",sizeof(a));
//sizeof中的a代表整个数组,12*4=48
    printf("%d\n",sizeof(a[0][0]));
//第一行第一个元素,4

注意strlen是找\0来结束运算的,而字符串后会有一个隐藏的\0

printf("%d\n",sizeof(a[0]));
//实际上a[0]是第一行一维数组的数组名,那么
//sizeof中的数组名代表整个数组
//而数组名a[0]其实就是个指针,指向4个int
//二维数组中第一行的大小,第一行4个元素
//结果是4*4=16

 练习4:char*p
printf("%d\n",sizeof(a[0]+1));
//a[0]的类型是int[4],作为指针就是int*,指向int
//向后加一的步长是int,指向第一行下标为1的元素
//输出结果是4/8

第一句的意思是,char*指针p指向后面字符串首元素a的地址。

printf("%d\n",sizeof(*(a[0]+1)));
//a[0]+1代表指向 第一行下标为1的元素 的指针
//解引用后代表访问这个元素
//元素类型int,那么结果为4

对比 
printf("%d\n",sizeof(a+1));
//a的类型是int[3][4]
//作为指针的话是一个一维数组int[4]*
//+1的步长是4个int,那么a+1就代表第二行的地址
//结果是4/8
    printf("%d\n",sizeof(*(a+1)));
//*(a+1)=a[1],是第二行的一维数组名
//sizeof中的数组名代表整个数组的大小
//此时的a[1]是第二行,有4个元素
//那就是4个int,16byte

依旧注意p是个指向a的指针,strlen主要看\0的位置。

printf("%d\n",sizeof(&a[0]+1));
//a[0]代表第一行的数组名
//&a[0]就代表指向第一行的指针
//此时的+1步长是 一行数组 的大小
//指针指向第二行的一维数组
//结果4/8
    printf("%d\n",sizeof(*(&a[0]+1)));
//&a[0]+1代表 指向第二行一维数组的指针
//对指针解引用就是a[1]
//结果4*4=16

 练习5:二维数组
printf("%d\n",sizeof(*a));
//a的类型是int[3][4],作为指针是int[4]*
//解引用后是int[4]
//那么*a就代表第一行的数组名
//实际上*a=*(a+0)=a[0]
//那么结果是4*4=16
    printf("%d\n",sizeof(a[3]));
//二维数组一共三行,最大是a[2]
//sizeof中的a[3]结果是这个表达式的类型大小
//所以不涉及越界访问的问题
//直接就是一行元素的大小,16

二维数组的话要明白,二维数组的数组名在作为指针的情况下是个一维数组指针。

int main()
{
    int a[5] = { 1, 2, 3, 4, 5 };
    int *ptr = (int *)(&a + 1);
    printf( "%d,%d", *(a + 1), *(ptr - 1));
    return 0; }
//由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
 int Num;
 char *pcName;
 short sDate;
 char cha[2];
 short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
 printf("%p\n", p + 0x1);
 printf("%p\n", (unsigned long)p + 0x1);
 printf("%p\n", (unsigned int*)p + 0x1);
 return 0; }
还不完全一样
我们得看指针类型,才能确定相邻的指针会差多少字节。
非要说有的话,也只有在按照不同数据进行分配空间后才算是有了类型。
而地址在有了类型后,也就是指针了。
以字节的方式来理解地址和指针

 练习6:判断程序结果
//假设p 的值为0x100000。 如下表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
     printf("%p\n", p + 0x1);
//p+1的步长是20字节 - 十六进制是14
//0x100014
     printf("%p\n", (unsigned long)p + 0x1);
//注意是把p这个结构体指针转换成一个整型了
//所以是加了十六进制的数字1
//结果是0x100001
     printf("%p\n", (unsigned int*)p + 0x1);
//p+1的步长是4或者8字节(32位/64位系统)
//0x100004

首先看*(a+1),a+1代表下标为1的地址,*(a+1)=a[1],结果是2

*(ptr - 1),先分析ptr接收了谁的地址

&a+1 代表 &a是整个数组的地址,+1则是指向a后面一个数组大小的地址

int*强制类型转换后,只能访问一个int大小的地址并存到ptr中,

此时ptr指向a[4]这个元素后面的地址,ptr是int*指针

那么ptr-1的步长是一个int,ptr-1指向a[4],解引用后就是5

output:2,5

练习7:第一眼没看出来的考点(重点看)
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; }

说实话,这个题我做过一遍,第二遍还是没看出来在干嘛

然后我思考了一下原因,就在于我被0x1迷惑了。我一看到0x1完全忘了指针p的作用,只想着地址加1是怎么加啊?

我一直在想地址加1难道不是纯粹的加1吗?这题为啥要这么写?

还真不是!!!

***指针和地址的新理解***

因为你要知道的是其实指针就是地址,地址就是指针

但是还有一个问题,那为什么普通的整型+1就是数值上的+1而不是字节上的+1呢?,因为如果单纯的说地址,那地址是一个储存单元一个地址,相邻的地址会相差1个字节,但是相邻的指针会相差一个字节吗?

那还真不是,地址之间相邻差1byte,所以+1 的步长就是1byte

那么话说回来了,地址会有类型吗? 那还真没有,如果 (int)a (int)a+1
但是并没有看到说对地址分类的说法,所以我建议是 ,也就是说,无论是地址还是字节,在分析+1步长的时候,得 看看它指向的类型占了几个字节来判断。
我觉得这个理解超级对!!以后的我,常回来看看,有新的理解一定要及时改动! 分析题 现在来分析题目,其实题目考察的就是指针类型对于+1步长的影响。
 
 练习8:%x?int+1?(又被击败) 
 
 
 

先分析ptr1指针接收到的地址是谁

&a是整个数组的大小,+1后指向a数组后面 一个数组大小 的空间

int*强制类型转换后就只能访问一个int大小的空间,把这个空间(也就是a[3]后面的int空间)存到了ptr1中

对于非指针+1的步长理解(地址神奇~)

接着是ptr2,首先a是指向数组首元素的,但是被强制转换为了int类型的数据,那么(int)a+1的步长会是多少呢?

int*+1会跳过4个字节,因为指针指向的int占4byte

char*+1跳过1个字节,因为指针指向的char占1byte

类似的,

我们的int+1是多大呢?难道是单纯的1+1=2吗?

我的思考过程是这样

        首先a是数组名,指向首元素的地址,在int强制转换后,是把一个地址强制转换为了int类型。按理说,int+1应该是按照一个int大小来向后挪动的啊!但是这样的话,int*指针又该怎么解释步长是一个int大小呢?

        关于这个问题,其实上个练习也涉及到了。需要知道的是我们在转换为int的时候,是把地址转换为了int类型的数据,也就是说此时的地址不是指向某个数据的指针,而是单纯的数字,其实就是给地址编号加了个数字1,而。

那这时候我们知道了(int)a+1就是a内存中第二个字节的地址,此时把这个地址转换为int*指针,而一个int*指针可以访问4个字节。我们按照小端储存来看看此时的ptr储存的是哪些。

00000002那么 p 指向的就是 a 数组中第一行的前四个元素。a作为指针使用的时候就是int[5]*指针*(a+i)=a[ i ]
010000
00(我加了#,因为没有0x看着别扭)int main() {    int a[3][2] = { (0, 1), (2, 3), (4, 5) };    int *p;    p = a[0];    printf( "%d", p[0]); return 0; }int a[3][2] = { (0, 1), (2, 3), (4, 5) }; int a[3][2] = { 1, 3, 5 };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; }

上面标红的就是ptr指向的内存单元。

那么打印的结果

ptr[-1]是指向ptr指针前面一个int空间的数据,这个空间的数据是a[3]。

按照小端储存的内存中是(低地址)04 00 00 00(高地址)

因为我们把数据存放到内存中按照小端储存,那就是数据的低位放到低地址,高位放到高地址,所以打印出来就是(高位)00 00 00 04 (低位)(省略前面的0)

*ptr则是解引用ptr指针,去通过这个地址访问,访问到的是(低地址)00 00 00 02(高地址)

因为我们把数据存放到内存中按照小端储存,那就是数据的低位放到低地址,高位放到高地址,打印出来的就是(高位)02 00 00 00(低位)(省略前面的0)

看结果a[4]的意思就是a向后挪动了4个步长,每个步长是int[5],那么此时的a[4]已经跳过了4*5=20个int元素

 地址转换为int+1,就是单纯的数字+1啦,实际上就是跳过了一个byte的地址。

练习9:有点陷阱
p[4]就代表p指针向后挪动了4个步长,每个步长是int[4],一共跳过了4*4=16个int元素

这题其实挺搞笑的,要是看走眼就无了

你要注意的是后面是逗号表达式,所以第一行的语句实际上就是

总结:二维数组

a[0]代表二维数组第一行,也就是1和3两个元素。

我们的int*p指向指向了a[0]这个一维数组,也就是说我们指向了第一行int[2]大小的首元素地址,因为p是int*指针,便一次指向一个int大小的空间,也就是指向首元素1。

而打印中的p[0]实际上就等价于*(p+0),也就是首元素1。

练习10:二维数组指针运用、指针运算、%p打印整型
p [4][2]=* ( * ( p + 4 ) + 2 );   

a的类型是int[5][5]二维数组,我们的p是int[4]*类型的指针,指向int[4]。

此时我们把p=a,而 a 没有被&也没有在sizeof当中,所以是数组首元素的地址,也就是第一行

因为地址无所谓正负,所以%p认为内存中存的就是一个地址,直接按照内存中的补码输出。

接着分析&p[4][2]和&a[4][2]什么意思

        首先我们知道p是指向a数组中第一行前四个元素的指针,是一个int[4]*的指针。

        a我们说是int[5][5]类型,而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; },那么此时a+1的步长就会是一个int[5]的大小,而我们又知道ptr1指向的就是aa数组最后一个元素后面的一个int大小的空间,代表a这个指针向后挪动了 i 步并访问这个空间的元素。那么int main() { char *a[] = {"work","at","alibaba"}; char**pa = a; pa++; printf("%s\n", *pa); return 0; }。此时的a[4]是一个int[5]类型的数组,把a[4]看做这个int[5]的数组名,那么a[4]数组名在作为指针使用的时候就是int*指针,那么a[4][2]就代表访问了a[4]数组中下标为2的元素。

        同样的道理,p是一个int[4]*的指针,我们又知道p的地址是a数组首元素的地址,指向a数组第一行的前四个元素,那么它+1的步长是int[4],"work" 字符串  是一堆char类型数据的集合,也就是char[5]数组,此时的p[4]就是一个int[4]类型的数组名,而p[4]数组名在作为指针使用的时候就是int*指针,p[4][2]就代表访问了p[4]数组中下标为2的元素。

“work”字符串就相当于char[5]的数组名,

数组名在运用的时候会变为指针,也就是会从char[5]数组变为char*指针(注:sizeof和&中的数组名除外)

我画个图,配合来看。

此时p[4][2]和a[4][2]的地址相4个int,也就是16个byte。

要知道 &p[4][2] - &a[4][2]是指针之间的元素个数,又因为p的地址比a的地址低,那么%d的形式打印出来就是-4,而%p的话则需要把-4的补码写出来,按照16进制数字进行输出。

那么为什么%p就不用像%d一样进行原码转换输出,而是直接使用补码呢?

数组名“work”作为指针的时候是指向首元素的,也就是指向w这个字符

这里我们演示一下

要达到,一眼能看出来代码在说什么的水平,这题就算OK了。

放一下结果

练习11:二维数组
当我们在char*[ ]数组中存放“work”字符串的时候,是存放了字符w的地址。

这题比较简单。

首先&aa是取出来了整个二维数组大小的地址,向后+1的步长是一个二维数组的大小,转换为int*指针便只能访问一个int,那么如图。

*(ptr-1)就是指ptr指针向前挪了一个int大小,正好指向数组最后一个元素,然后访问住户,那么结果就是10。

同样的道理,

aa是int[2][5]数组,在作为指针运用的时候是一个int[5]*指针,指向第一行的一维int[5]数组,+1的步长就是指向第二行的一维数组int[5],加上*解引用代表访问这个数组,那么也就相当于aa[1]。

实际上也可直接利用*(a+i)=a[ i ],上面只是详细解释了一下。

aa[1]的类型是int[5],此时被强制转化为int*指针,那便只能指向一个int大小的空间,也就是指向a[1][0],那么ptr2就是指向了a[1][0]。

*(ptr2-1)就是向前挪了一个int大小,指向a[0][4]元素,*代表访问元素,结果就是5。

如图。 

练习12:char*a[ ]
 char**pa = a;

        首先我们要理解char*a[ ]初始化意味着什么,要知道数组中存放的是char*类型的数据,而我们初始化的时候放的是字符串,那么字符串又意味着什么?

        可以看到  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; }。那么 char *c[] = {"ENTER","NEW","POINT","FIRST"}; char**cp[] = {c+3,c+2,c+1,c};而我们又知道,char**[ ]则代表我们存放的数据是char**这个二级指针数据。所以实际上我们存放的就是char*类型的数据,还需要知道的是数组名c存放了首元素char*的地址。

        也就是说,实际上c,c+1,c+2,c+3就是char**类型的数据

c,c+1,c+2,c+3便分别指向c数组中下标为0,1,2,3,4,的元素

而这些元素的类型都是char*类型,

上面这个语句的意思是,现在我们用pa指针指向a数组中首元素的地址,指向的数据类型是char*。

也就是说指向第一个char*。如图。

         pa++是指向第二个char*的指针,现在对指针进行*解引用,也就是访问这个char*元素,而我们在打印的时候需要传递的就是char*数据,这个char*指向字符串“at”当中'a'的地址,所以打印的结果是at。

练习13:char***之终极练习
c,c+1,c+2,c+3这四个char**元素,分别存放了下标为0,1,2,3,4,的char*元素的地址。(指向谁,存放谁的地址)

 首先梳理第一句的意思,我们在上面已经分析过char*[ ]当中存放字符串意味着什么,这里便只放图了。

 存放了四个字符串首元素的地址。

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);,那么我们又知道c是char*[4]数组,是一个数组名,而数组名在运用的时候就会变为指向首元素的指针,也就是从char*[4]类型变为char**指针,代表指向char*数据的指针,也就是 *++cpp=c+2。所以 printf("%s\n", **++cpp); printf("%s\n", *--*++cpp+3); printf("%s\n", *cpp[-2]+3); printf("%s\n", cpp[-1][-1]+1);,又因为c是指向数组首元素的,那么c+1,c+2,c+3便是指针c向后移动的步数,c是一个char**指针,移动的步长是char*,那么此时的 第二句的cpp是上一句的++cpp,是指向c+2的。总体过程是:*--*++cpp + 3 = *--(c+1)+ 3 = *c+3 = "ENTER" + 3 = "ER"也就是 "ENTER"(字符串数组名,实际上是首元素'E'的地址,类型char*)

 

接着是第三句。

 printf("%s\n", **++cpp);
 printf("%s\n", *--*++cpp+3);
 printf("%s\n", *cpp[-2]+3);
 printf("%s\n", cpp[-1][-1]+1);
 char***是指向char**数据的指针,此时的cp是指向首元素c+3这个char**的char***指针,把cp的地址交给cpp,也就代表cpp和cp一样,是指向首元素c+3这个char**的char***指针,也就是存放了c+3这个char**的地址。
*cpp[-2]+3 = *(*(cpp-2))+3 = *(c+3)+3 = "FIRST" + 3 = "ST"

先分析第一句,++cpp代表cpp向后挪动了一步,这一步的大小是一个char**。++cpp的类型是char***,对++cpp进行解引用,*++cpp就是char**类型,也就是在访问++cpp所指向的对象,此时

++cpp指向c+2,那么"FIRST"(字符串数组名,实际上是首元素'F'的地址,类型char*)

再进行解引用的话,就 相当于对c+2进行解引用,c+2指向的对象是  "POINT" 的 首字符 'P' 地址,对c+2进行解引用代表访问  c+2所指向的对象,也就是访问"POINT" 的 首字符 'P' 地址,此时把这个地址用%s的形式打印, 那么便是POINT。 如图。

 现在分析第二句。

 printf("%s\n", **++cpp);
 printf("%s\n", *--*++cpp+3);
 printf("%s\n", *cpp[-2]+3);
 printf("%s\n", cpp[-1][-1]+1);

 我们需要知道的是前置后置++和--都会对数据本身造成影响,也就是说cpp[-1][-1]+1 = *(*(cpp-1)-1)+ 1 = *((c+2)-1)+1 = *(c+1)+1 = "NEW"+1=其次,我们要先运算*--*++cpp+3中的++cpp。那么++cpp指向c+1,解引用的话就是访问++cpp指向的对象,++cpp指向c+1,那么*++cpp=c+1,然后我们再对c+1进行前置--,那么c+1就会变为c。再对c进行解引用,就是在访问c所指向的对象,c指向ENTER字符串中E的地址,再对这个地址+3就意味着,从字符E向后走了3步,每步大小是一个字符char,那么打印结果是ER。

"EW" "NEW"(字符串数组名,实际上是首元素'N'的地址,类型char*)

 分析第三句

直接写出整体运算过程
此时的cpp指向cp数组中第三个元素,(不再细说,道理想通) [+++] 分析最后一句
[+++]

因为上一句并不是使用的前置后置++或者--,所以cpp还是指向cp数组中第三个元素。

[+++][+++]

[+++]

 这题达到能[+++]的水平就可以了。

练习到此,后续还会有练习,不过这算是比较深入的练习了,如果我没解释清楚,可以看我之前对于指针文章的解释,或者直接私信我,很乐意解答,可惜没人看哈哈哈

)
File: /www/wwwroot/outofmemory.cn/tmp/route_read.php, Line: 126, InsideLink()
File: /www/wwwroot/outofmemory.cn/tmp/index.inc.php, Line: 166, include(/www/wwwroot/outofmemory.cn/tmp/route_read.php)
File: /www/wwwroot/outofmemory.cn/index.php, Line: 30, include(/www/wwwroot/outofmemory.cn/tmp/index.inc.php)
Error[8]: Undefined offset: 122, File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 121
File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 473, decode(

上一篇:从0开始学c语言-29-数据储存练习、筑基期练习、两步翻转_阿秋的阿秋不是阿秋的博客-CSDN博客

应有的基础:

从0开始学c语言-28-qsort函数、 数组和指针参数、函数指针数组(转移表)、回调函数_阿秋的阿秋不是阿秋的博客-CSDN博客

从0开始学c语言-27-字符指针,指针数组和数组指针_阿秋的阿秋不是阿秋的博客-CSDN博客

目录

练习1:int arr[ ]

 练习2:char arr[ ] zifu

 对比

 练习3:char arr[ ]

 对比\0

 练习4:char*p

对比 

 练习5:二维数组

 练习6:判断程序结果

练习7:第一眼没看出来的考点(重点看)

***指针和地址的新理解***

分析题

练习8:%x?int+1?(又被击败)

对于非指针+1的步长理解(地址神奇~)

练习9:有点陷阱

练习10:二维数组指针运用、指针运算、%p打印整型

练习11:二维数组

练习12:char*a[ ]

练习13:char***之终极练习


练习1:int arr[ ]

应有的知识:

数组名的意义: 1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。 2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。 3. 除此之外所有的数组名都表示首元素的地址。
//一维数组
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a+0));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(a[1]));
printf("%d\n",sizeof(&a));
printf("%d\n",sizeof(*&a));
printf("%d\n",sizeof(&a+1));
printf("%d\n",sizeof(&a[0]));
printf("%d\n",sizeof(&a[0]+1));

请思考每个printf函数输出的结果是多少。

开始讲

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));

a单纯的数组名实际上就是数组首元素的地址,那么地址的大小在32位系统上是4byte,在64位系统上是8byte大小。但是呢,sizeof当中的数组名则代表整个数组,这一点要特别注意,那么a数组的大小就是4个int的大小,也就是16byte。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a+0));

实际上地址就是指针,那么a+0就代表a这个指针向后挪动了0步。且a本身指向数组首元素,+0表示一步没有挪动。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(*a));

我们知道a是指针,*a就是对指针进行解引用,a指向数组首元素,那么*a就是在访问数组首元素,而数组首元素的类型是int,int的大小是4byte。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a+1));

a+0和a+1都是指针,只不过一个指向首元素,一个指向下标为1的元素。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a[1]));

a[1]代表数组下标为1的元素,而这个元素的类型是int,那么就是4byte。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(&a));

我们早在前面说过,

1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。 2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
那么现在是sizeof和&数组名组合到一起,这时候,sizeof当中的&数组名,则是计算的 【指向整个数组的指针】的大小。
printf("%d\n",sizeof(*&a));

现在在&a前面加上了*, 而*&a=a,a又在sizeof当中,那就代表我们访问了整个数组,应该是16byte。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(&a+1));

&a代表指向整个数组的指针,那么+1则会让指针向后挪动一步,这一步的大小是整个数组的大小,我们计算的还是 指针的大小。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(&a[0]));

&a[0]是什么意思?

如果讲结合性的话,我还不太熟练,只能放图监视给你看

 也就是说&a[0]=&(a[0])

意思是取出数组第一个元素的地址,本质上是个指针。
printf("%d\n",sizeof(&a[0]+1));

那么同样的,&a[0]+1就代表指向第一个元素的指针向后走了一步,而这一步的大小是一个int,也就是指向了下标为1的元素。

现在看看结果,是否符合我们的推理。

 练习2:char arr[ ] zifu
char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));
	printf("%d\n", sizeof(arr + 0));
	printf("%d\n", sizeof(*arr));
	printf("%d\n", sizeof(arr[1]));
	printf("%d\n", sizeof(&arr));
	printf("%d\n", sizeof(&arr + 1));
	printf("%d\n", sizeof(&arr[0] + 1));

实际上,这题和上面那道题几乎一模一样。我就把解释写在代码里了哈。

char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));
//整个数组的大小,6
	printf("%d\n", sizeof(arr + 0));
//指向下标1的指针,4/8 ,8代表64位系统的结果
	printf("%d\n", sizeof(*arr));
//访问数组第一个元素,1
	printf("%d\n", sizeof(arr[1]));
//访问数组第一个元素,1
	printf("%d\n", sizeof(&arr));
//指向整个数组的指针,4/8
	printf("%d\n", sizeof(&arr + 1));
//指向整个数组的指针向后挪动一步,4/8
	printf("%d\n", sizeof(&arr[0] + 1));
//指向数组第一个元素的指针向后挪动一步,4/8

 对比
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));

实际上大体也是一样的,只不过我们换成了strlen,那就要特别注意\0。

char arr[] = { 'a','b','c','d','e','f' };
    printf("%d\n", strlen(arr));
//没有char arr[] = "abcdef";
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr+0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr+1));
printf("%d\n", sizeof(&arr[0]+1));,随机值
    printf("%d\n", strlen(arr+0));
//没有char arr[] = "abcdef";
    printf("%d\n", sizeof(arr));
//sizeof中的数组名代表整个数组,7(记得有隐藏的char arr[] = "abcdef";
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));元素)
    printf("%d\n", sizeof(arr+0));
//arr+0代表指针,指向第一个元素,4/8 (8是64位系统的结果)
    printf("%d\n", sizeof(*arr));
//代表字符串的第一个字符,类型char,1
    printf("%d\n", sizeof(arr[1]));
//代表字符串的第一个字符,类型char,1
    printf("%d\n", sizeof(&arr));
//指向整个char数组的指针,4/8
    printf("%d\n", sizeof(&arr+1));
//指向整个char数组的指针向后挪一步,4/8
    printf("%d\n", sizeof(&arr[0]+1));
//指向字符串第一个字符的指针向后挪一步,4/8,随机值
    printf("%d\n", strlen(*arr));
//*arr不是指针,不能计算
    printf("%d\n", strlen(arr[1]));
//arr[1]不是指针,不能计算
    printf("%d\n", strlen(&arr));
//没有char arr[] = "abcdef";
    printf("%d\n", strlen(arr));
//6
    printf("%d\n", strlen(arr+0));
//6
    printf("%d\n", strlen(*arr));
//不能计算,strlen要的是const char*指针类型的数据
    printf("%d\n", strlen(arr[1]));
//不能计算,strlen要的是const char*指针类型的数据
//虽然后面这两个语句不是const char*类型,但是可以把地址传过去
    printf("%d\n", strlen(&arr));
//6
    printf("%d\n", strlen(&arr+1));
//指向数组的指针向后挪动一步
//这一步的大小是一个数组的大小,打印结果随机值
    printf("%d\n", strlen(&arr[0]+1));
//指向数组中首元素的指针向后挪一步
//这步大小是一个char的大小,打印结果是5,随机值
    printf("%d\n", strlen(&arr+1));
//没有char *p = "abcdef";
printf("%d\n", sizeof(p));
printf("%d\n", sizeof(p+1));
printf("%d\n", sizeof(*p));
printf("%d\n", sizeof(p[0]));
printf("%d\n", sizeof(&p));
printf("%d\n", sizeof(&p+1));
printf("%d\n", sizeof(&p[0]+1));,随机值-6  因为是指向整个数组的指针向后挪一步
    printf("%d\n", strlen(&arr[0]+1));
//没有char *p = "abcdef";
    printf("%d\n", sizeof(p));
//p是个指针,4/8 (8是64位系统的结果)
    printf("%d\n", sizeof(p+1));
//p+1指向下一个字符c,4/8
    printf("%d\n", sizeof(*p));
//*p代表首字符字符a,1
    printf("%d\n", sizeof(p[0]));
//代表首字符字符a,1
    printf("%d\n", sizeof(&p));
//&p代表指针p的地址,4/8
    printf("%d\n", sizeof(&p+1));
//指向指针p的指针向后挪动一步,这一步大小是一个char*,4/8
    printf("%d\n", sizeof(&p[0]+1));
//指向首字符的指针向后挪动一步,这一步的大小是一个char,4/8,随机值-1 因为是指向一个元素的指针向后挪一步

很多警告,主要看类型不同的警告,正如我们所说,*arr和arr[0]是不能计算的,

在注释掉后,计算的结果就像我们注释里说的。

 练习3:char arr[ ]
char *p = "abcdef";
printf("%d\n", strlen(p));
printf("%d\n", strlen(p+1));
printf("%d\n", strlen(*p));
printf("%d\n", strlen(p[0]));
printf("%d\n", strlen(&p));
printf("%d\n", strlen(&p+1));
printf("%d\n", strlen(&p[0]+1));

要知道的是,储存字符串当中的字符数组名是第一个字符的地址。

char *p = "abcdef";
    printf("%d\n", strlen(p));
//6
    printf("%d\n", strlen(p+1));
//5
    printf("%d\n", strlen(*p));
    printf("%d\n", strlen(p[0]));
//上面的*p和p[0]是char类型,不能计算
    printf("%d\n", strlen(&p));
//p此时是个指向首元素的指针,而&p代表指向p的指针
//那么&p中保存的是p的地址,int a[3][4] = {0};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a[0][0]));
printf("%d\n",sizeof(a[0]));
printf("%d\n",sizeof(a[0]+1));
printf("%d\n",sizeof(*(a[0]+1)));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(*(a+1)));
printf("%d\n",sizeof(&a[0]+1));
printf("%d\n",sizeof(*(&a[0]+1)));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a[3]));位置不确定,计算值会是个随机值
    printf("%d\n", strlen(&p+1));
//&p是个指向p的指针,类型char**,向后挪动一步的大小是char*
//char*是个指针,指针的大小是4/8
//所以结果是随机值-4/8 (8是64位系统的)
    printf("%d\n", strlen(&p[0]+1));
//5

 对比\0
int a[3][4] = {0};
    printf("%d\n",sizeof(a));
//sizeof中的a代表整个数组,12*4=48
    printf("%d\n",sizeof(a[0][0]));
//第一行第一个元素,4

注意strlen是找\0来结束运算的,而字符串后会有一个隐藏的\0

printf("%d\n",sizeof(a[0]));
//实际上a[0]是第一行一维数组的数组名,那么
//sizeof中的数组名代表整个数组
//而数组名a[0]其实就是个指针,指向4个int
//二维数组中第一行的大小,第一行4个元素
//结果是4*4=16

 练习4:char*p
printf("%d\n",sizeof(a[0]+1));
//a[0]的类型是int[4],作为指针就是int*,指向int
//向后加一的步长是int,指向第一行下标为1的元素
//输出结果是4/8

第一句的意思是,char*指针p指向后面字符串首元素a的地址。

printf("%d\n",sizeof(*(a[0]+1)));
//a[0]+1代表指向 第一行下标为1的元素 的指针
//解引用后代表访问这个元素
//元素类型int,那么结果为4

对比 
printf("%d\n",sizeof(a+1));
//a的类型是int[3][4]
//作为指针的话是一个一维数组int[4]*
//+1的步长是4个int,那么a+1就代表第二行的地址
//结果是4/8
    printf("%d\n",sizeof(*(a+1)));
//*(a+1)=a[1],是第二行的一维数组名
//sizeof中的数组名代表整个数组的大小
//此时的a[1]是第二行,有4个元素
//那就是4个int,16byte

依旧注意p是个指向a的指针,strlen主要看\0的位置。

printf("%d\n",sizeof(&a[0]+1));
//a[0]代表第一行的数组名
//&a[0]就代表指向第一行的指针
//此时的+1步长是 一行数组 的大小
//指针指向第二行的一维数组
//结果4/8
    printf("%d\n",sizeof(*(&a[0]+1)));
//&a[0]+1代表 指向第二行一维数组的指针
//对指针解引用就是a[1]
//结果4*4=16

 练习5:二维数组
printf("%d\n",sizeof(*a));
//a的类型是int[3][4],作为指针是int[4]*
//解引用后是int[4]
//那么*a就代表第一行的数组名
//实际上*a=*(a+0)=a[0]
//那么结果是4*4=16
    printf("%d\n",sizeof(a[3]));
//二维数组一共三行,最大是a[2]
//sizeof中的a[3]结果是这个表达式的类型大小
//所以不涉及越界访问的问题
//直接就是一行元素的大小,16

二维数组的话要明白,二维数组的数组名在作为指针的情况下是个一维数组指针。

int main()
{
    int a[5] = { 1, 2, 3, 4, 5 };
    int *ptr = (int *)(&a + 1);
    printf( "%d,%d", *(a + 1), *(ptr - 1));
    return 0; }
//由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
 int Num;
 char *pcName;
 short sDate;
 char cha[2];
 short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
 printf("%p\n", p + 0x1);
 printf("%p\n", (unsigned long)p + 0x1);
 printf("%p\n", (unsigned int*)p + 0x1);
 return 0; }
还不完全一样
我们得看指针类型,才能确定相邻的指针会差多少字节。
非要说有的话,也只有在按照不同数据进行分配空间后才算是有了类型。
而地址在有了类型后,也就是指针了。
以字节的方式来理解地址和指针

 练习6:判断程序结果
//假设p 的值为0x100000。 如下表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
     printf("%p\n", p + 0x1);
//p+1的步长是20字节 - 十六进制是14
//0x100014
     printf("%p\n", (unsigned long)p + 0x1);
//注意是把p这个结构体指针转换成一个整型了
//所以是加了十六进制的数字1
//结果是0x100001
     printf("%p\n", (unsigned int*)p + 0x1);
//p+1的步长是4或者8字节(32位/64位系统)
//0x100004

首先看*(a+1),a+1代表下标为1的地址,*(a+1)=a[1],结果是2

*(ptr - 1),先分析ptr接收了谁的地址

&a+1 代表 &a是整个数组的地址,+1则是指向a后面一个数组大小的地址

int*强制类型转换后,只能访问一个int大小的地址并存到ptr中,

此时ptr指向a[4]这个元素后面的地址,ptr是int*指针

那么ptr-1的步长是一个int,ptr-1指向a[4],解引用后就是5

output:2,5

练习7:第一眼没看出来的考点(重点看)
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; }

说实话,这个题我做过一遍,第二遍还是没看出来在干嘛

然后我思考了一下原因,就在于我被0x1迷惑了。我一看到0x1完全忘了指针p的作用,只想着地址加1是怎么加啊?

我一直在想地址加1难道不是纯粹的加1吗?这题为啥要这么写?

还真不是!!!

***指针和地址的新理解***

因为你要知道的是其实指针就是地址,地址就是指针

但是还有一个问题,那为什么普通的整型+1就是数值上的+1而不是字节上的+1呢?,因为如果单纯的说地址,那地址是一个储存单元一个地址,相邻的地址会相差1个字节,但是相邻的指针会相差一个字节吗?

那还真不是,地址之间相邻差1byte,所以+1 的步长就是1byte

那么话说回来了,地址会有类型吗? 那还真没有,如果 (int)a (int)a+1
但是并没有看到说对地址分类的说法,所以我建议是 ,也就是说,无论是地址还是字节,在分析+1步长的时候,得 看看它指向的类型占了几个字节来判断。
我觉得这个理解超级对!!以后的我,常回来看看,有新的理解一定要及时改动! 分析题 现在来分析题目,其实题目考察的就是指针类型对于+1步长的影响。
 
 练习8:%x?int+1?(又被击败) 
 
 
 

先分析ptr1指针接收到的地址是谁

&a是整个数组的大小,+1后指向a数组后面 一个数组大小 的空间

int*强制类型转换后就只能访问一个int大小的空间,把这个空间(也就是a[3]后面的int空间)存到了ptr1中

对于非指针+1的步长理解(地址神奇~)

接着是ptr2,首先a是指向数组首元素的,但是被强制转换为了int类型的数据,那么(int)a+1的步长会是多少呢?

int*+1会跳过4个字节,因为指针指向的int占4byte

char*+1跳过1个字节,因为指针指向的char占1byte

类似的,

我们的int+1是多大呢?难道是单纯的1+1=2吗?

我的思考过程是这样

        首先a是数组名,指向首元素的地址,在int强制转换后,是把一个地址强制转换为了int类型。按理说,int+1应该是按照一个int大小来向后挪动的啊!但是这样的话,int*指针又该怎么解释步长是一个int大小呢?

        关于这个问题,其实上个练习也涉及到了。需要知道的是我们在转换为int的时候,是把地址转换为了int类型的数据,也就是说此时的地址不是指向某个数据的指针,而是单纯的数字,其实就是给地址编号加了个数字1,而。

那这时候我们知道了(int)a+1就是a内存中第二个字节的地址,此时把这个地址转换为int*指针,而一个int*指针可以访问4个字节。我们按照小端储存来看看此时的ptr储存的是哪些。

00000002那么 p 指向的就是 a 数组中第一行的前四个元素。a作为指针使用的时候就是int[5]*指针*(a+i)=a[ i ]
010000
00(我加了#,因为没有0x看着别扭)int main() {    int a[3][2] = { (0, 1), (2, 3), (4, 5) };    int *p;    p = a[0];    printf( "%d", p[0]); return 0; }int a[3][2] = { (0, 1), (2, 3), (4, 5) }; int a[3][2] = { 1, 3, 5 };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; }

上面标红的就是ptr指向的内存单元。

那么打印的结果

ptr[-1]是指向ptr指针前面一个int空间的数据,这个空间的数据是a[3]。

按照小端储存的内存中是(低地址)04 00 00 00(高地址)

因为我们把数据存放到内存中按照小端储存,那就是数据的低位放到低地址,高位放到高地址,所以打印出来就是(高位)00 00 00 04 (低位)(省略前面的0)

*ptr则是解引用ptr指针,去通过这个地址访问,访问到的是(低地址)00 00 00 02(高地址)

因为我们把数据存放到内存中按照小端储存,那就是数据的低位放到低地址,高位放到高地址,打印出来的就是(高位)02 00 00 00(低位)(省略前面的0)

看结果a[4]的意思就是a向后挪动了4个步长,每个步长是int[5],那么此时的a[4]已经跳过了4*5=20个int元素

 地址转换为int+1,就是单纯的数字+1啦,实际上就是跳过了一个byte的地址。

练习9:有点陷阱
p[4]就代表p指针向后挪动了4个步长,每个步长是int[4],一共跳过了4*4=16个int元素

这题其实挺搞笑的,要是看走眼就无了

你要注意的是后面是逗号表达式,所以第一行的语句实际上就是

总结:二维数组

a[0]代表二维数组第一行,也就是1和3两个元素。

我们的int*p指向指向了a[0]这个一维数组,也就是说我们指向了第一行int[2]大小的首元素地址,因为p是int*指针,便一次指向一个int大小的空间,也就是指向首元素1。

而打印中的p[0]实际上就等价于*(p+0),也就是首元素1。

练习10:二维数组指针运用、指针运算、%p打印整型
p [4][2]=* ( * ( p + 4 ) + 2 );   

a的类型是int[5][5]二维数组,我们的p是int[4]*类型的指针,指向int[4]。

此时我们把p=a,而 a 没有被&也没有在sizeof当中,所以是数组首元素的地址,也就是第一行

因为地址无所谓正负,所以%p认为内存中存的就是一个地址,直接按照内存中的补码输出。

接着分析&p[4][2]和&a[4][2]什么意思

        首先我们知道p是指向a数组中第一行前四个元素的指针,是一个int[4]*的指针。

        a我们说是int[5][5]类型,而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; },那么此时a+1的步长就会是一个int[5]的大小,而我们又知道ptr1指向的就是aa数组最后一个元素后面的一个int大小的空间,代表a这个指针向后挪动了 i 步并访问这个空间的元素。那么int main() { char *a[] = {"work","at","alibaba"}; char**pa = a; pa++; printf("%s\n", *pa); return 0; }。此时的a[4]是一个int[5]类型的数组,把a[4]看做这个int[5]的数组名,那么a[4]数组名在作为指针使用的时候就是int*指针,那么a[4][2]就代表访问了a[4]数组中下标为2的元素。

        同样的道理,p是一个int[4]*的指针,我们又知道p的地址是a数组首元素的地址,指向a数组第一行的前四个元素,那么它+1的步长是int[4],"work" 字符串  是一堆char类型数据的集合,也就是char[5]数组,此时的p[4]就是一个int[4]类型的数组名,而p[4]数组名在作为指针使用的时候就是int*指针,p[4][2]就代表访问了p[4]数组中下标为2的元素。

“work”字符串就相当于char[5]的数组名,

数组名在运用的时候会变为指针,也就是会从char[5]数组变为char*指针(注:sizeof和&中的数组名除外)

我画个图,配合来看。

此时p[4][2]和a[4][2]的地址相4个int,也就是16个byte。

要知道 &p[4][2] - &a[4][2]是指针之间的元素个数,又因为p的地址比a的地址低,那么%d的形式打印出来就是-4,而%p的话则需要把-4的补码写出来,按照16进制数字进行输出。

那么为什么%p就不用像%d一样进行原码转换输出,而是直接使用补码呢?

数组名“work”作为指针的时候是指向首元素的,也就是指向w这个字符

这里我们演示一下

要达到,一眼能看出来代码在说什么的水平,这题就算OK了。

放一下结果

练习11:二维数组
当我们在char*[ ]数组中存放“work”字符串的时候,是存放了字符w的地址。

这题比较简单。

首先&aa是取出来了整个二维数组大小的地址,向后+1的步长是一个二维数组的大小,转换为int*指针便只能访问一个int,那么如图。

*(ptr-1)就是指ptr指针向前挪了一个int大小,正好指向数组最后一个元素,然后访问住户,那么结果就是10。

同样的道理,

aa是int[2][5]数组,在作为指针运用的时候是一个int[5]*指针,指向第一行的一维int[5]数组,+1的步长就是指向第二行的一维数组int[5],加上*解引用代表访问这个数组,那么也就相当于aa[1]。

实际上也可直接利用*(a+i)=a[ i ],上面只是详细解释了一下。

aa[1]的类型是int[5],此时被强制转化为int*指针,那便只能指向一个int大小的空间,也就是指向a[1][0],那么ptr2就是指向了a[1][0]。

*(ptr2-1)就是向前挪了一个int大小,指向a[0][4]元素,*代表访问元素,结果就是5。

如图。 

练习12:char*a[ ]
 char**pa = a;

        首先我们要理解char*a[ ]初始化意味着什么,要知道数组中存放的是char*类型的数据,而我们初始化的时候放的是字符串,那么字符串又意味着什么?

        可以看到  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; }。那么 char *c[] = {"ENTER","NEW","POINT","FIRST"}; char**cp[] = {c+3,c+2,c+1,c};而我们又知道,char**[ ]则代表我们存放的数据是char**这个二级指针数据。所以实际上我们存放的就是char*类型的数据,还需要知道的是数组名c存放了首元素char*的地址。

        也就是说,实际上c,c+1,c+2,c+3就是char**类型的数据

c,c+1,c+2,c+3便分别指向c数组中下标为0,1,2,3,4,的元素

而这些元素的类型都是char*类型,

上面这个语句的意思是,现在我们用pa指针指向a数组中首元素的地址,指向的数据类型是char*。

也就是说指向第一个char*。如图。

         pa++是指向第二个char*的指针,现在对指针进行*解引用,也就是访问这个char*元素,而我们在打印的时候需要传递的就是char*数据,这个char*指向字符串“at”当中'a'的地址,所以打印的结果是at。

练习13:char***之终极练习
c,c+1,c+2,c+3这四个char**元素,分别存放了下标为0,1,2,3,4,的char*元素的地址。(指向谁,存放谁的地址)

 首先梳理第一句的意思,我们在上面已经分析过char*[ ]当中存放字符串意味着什么,这里便只放图了。

 存放了四个字符串首元素的地址。

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);,那么我们又知道c是char*[4]数组,是一个数组名,而数组名在运用的时候就会变为指向首元素的指针,也就是从char*[4]类型变为char**指针,代表指向char*数据的指针,也就是 *++cpp=c+2。所以 printf("%s\n", **++cpp); printf("%s\n", *--*++cpp+3); printf("%s\n", *cpp[-2]+3); printf("%s\n", cpp[-1][-1]+1);,又因为c是指向数组首元素的,那么c+1,c+2,c+3便是指针c向后移动的步数,c是一个char**指针,移动的步长是char*,那么此时的 第二句的cpp是上一句的++cpp,是指向c+2的。总体过程是:*--*++cpp + 3 = *--(c+1)+ 3 = *c+3 = "ENTER" + 3 = "ER"也就是 "ENTER"(字符串数组名,实际上是首元素'E'的地址,类型char*)

 

接着是第三句。

 printf("%s\n", **++cpp);
 printf("%s\n", *--*++cpp+3);
 printf("%s\n", *cpp[-2]+3);
 printf("%s\n", cpp[-1][-1]+1);
 char***是指向char**数据的指针,此时的cp是指向首元素c+3这个char**的char***指针,把cp的地址交给cpp,也就代表cpp和cp一样,是指向首元素c+3这个char**的char***指针,也就是存放了c+3这个char**的地址。
*cpp[-2]+3 = *(*(cpp-2))+3 = *(c+3)+3 = "FIRST" + 3 = "ST"

先分析第一句,++cpp代表cpp向后挪动了一步,这一步的大小是一个char**。++cpp的类型是char***,对++cpp进行解引用,*++cpp就是char**类型,也就是在访问++cpp所指向的对象,此时

++cpp指向c+2,那么"FIRST"(字符串数组名,实际上是首元素'F'的地址,类型char*)

再进行解引用的话,就 相当于对c+2进行解引用,c+2指向的对象是  "POINT" 的 首字符 'P' 地址,对c+2进行解引用代表访问  c+2所指向的对象,也就是访问"POINT" 的 首字符 'P' 地址,此时把这个地址用%s的形式打印, 那么便是POINT。 如图。

 现在分析第二句。

 printf("%s\n", **++cpp);
 printf("%s\n", *--*++cpp+3);
 printf("%s\n", *cpp[-2]+3);
 printf("%s\n", cpp[-1][-1]+1);

 我们需要知道的是前置后置++和--都会对数据本身造成影响,也就是说cpp[-1][-1]+1 = *(*(cpp-1)-1)+ 1 = *((c+2)-1)+1 = *(c+1)+1 = "NEW"+1=其次,我们要先运算*--*++cpp+3中的++cpp。那么++cpp指向c+1,解引用的话就是访问++cpp指向的对象,++cpp指向c+1,那么*++cpp=c+1,然后我们再对c+1进行前置--,那么c+1就会变为c。再对c进行解引用,就是在访问c所指向的对象,c指向ENTER字符串中E的地址,再对这个地址+3就意味着,从字符E向后走了3步,每步大小是一个字符char,那么打印结果是ER。

"EW" "NEW"(字符串数组名,实际上是首元素'N'的地址,类型char*)

 分析第三句

直接写出整体运算过程
此时的cpp指向cp数组中第三个元素,(不再细说,道理想通) 分析最后一句
[+++]

因为上一句并不是使用的前置后置++或者--,所以cpp还是指向cp数组中第三个元素。

[+++][+++]

[+++]

 这题达到能[+++]的水平就可以了。

练习到此,后续还会有练习,不过这算是比较深入的练习了,如果我没解释清楚,可以看我之前对于指针文章的解释,或者直接私信我,很乐意解答,可惜没人看哈哈哈

)
File: /www/wwwroot/outofmemory.cn/tmp/route_read.php, Line: 126, InsideLink()
File: /www/wwwroot/outofmemory.cn/tmp/index.inc.php, Line: 166, include(/www/wwwroot/outofmemory.cn/tmp/route_read.php)
File: /www/wwwroot/outofmemory.cn/index.php, Line: 30, include(/www/wwwroot/outofmemory.cn/tmp/index.inc.php)
Error[8]: Undefined offset: 123, File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 121
File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 473, decode(

上一篇:从0开始学c语言-29-数据储存练习、筑基期练习、两步翻转_阿秋的阿秋不是阿秋的博客-CSDN博客

应有的基础:

从0开始学c语言-28-qsort函数、 数组和指针参数、函数指针数组(转移表)、回调函数_阿秋的阿秋不是阿秋的博客-CSDN博客

从0开始学c语言-27-字符指针,指针数组和数组指针_阿秋的阿秋不是阿秋的博客-CSDN博客

目录

练习1:int arr[ ]

 练习2:char arr[ ] zifu

 对比

 练习3:char arr[ ]

 对比\0

 练习4:char*p

对比 

 练习5:二维数组

 练习6:判断程序结果

练习7:第一眼没看出来的考点(重点看)

***指针和地址的新理解***

分析题

练习8:%x?int+1?(又被击败)

对于非指针+1的步长理解(地址神奇~)

练习9:有点陷阱

练习10:二维数组指针运用、指针运算、%p打印整型

练习11:二维数组

练习12:char*a[ ]

练习13:char***之终极练习


练习1:int arr[ ]

应有的知识:

数组名的意义: 1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。 2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。 3. 除此之外所有的数组名都表示首元素的地址。
//一维数组
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a+0));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(a[1]));
printf("%d\n",sizeof(&a));
printf("%d\n",sizeof(*&a));
printf("%d\n",sizeof(&a+1));
printf("%d\n",sizeof(&a[0]));
printf("%d\n",sizeof(&a[0]+1));

请思考每个printf函数输出的结果是多少。

开始讲

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));

a单纯的数组名实际上就是数组首元素的地址,那么地址的大小在32位系统上是4byte,在64位系统上是8byte大小。但是呢,sizeof当中的数组名则代表整个数组,这一点要特别注意,那么a数组的大小就是4个int的大小,也就是16byte。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a+0));

实际上地址就是指针,那么a+0就代表a这个指针向后挪动了0步。且a本身指向数组首元素,+0表示一步没有挪动。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(*a));

我们知道a是指针,*a就是对指针进行解引用,a指向数组首元素,那么*a就是在访问数组首元素,而数组首元素的类型是int,int的大小是4byte。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a+1));

a+0和a+1都是指针,只不过一个指向首元素,一个指向下标为1的元素。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a[1]));

a[1]代表数组下标为1的元素,而这个元素的类型是int,那么就是4byte。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(&a));

我们早在前面说过,

1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。 2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
那么现在是sizeof和&数组名组合到一起,这时候,sizeof当中的&数组名,则是计算的 【指向整个数组的指针】的大小。
printf("%d\n",sizeof(*&a));

现在在&a前面加上了*, 而*&a=a,a又在sizeof当中,那就代表我们访问了整个数组,应该是16byte。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(&a+1));

&a代表指向整个数组的指针,那么+1则会让指针向后挪动一步,这一步的大小是整个数组的大小,我们计算的还是 指针的大小。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(&a[0]));

&a[0]是什么意思?

如果讲结合性的话,我还不太熟练,只能放图监视给你看

 也就是说&a[0]=&(a[0])

意思是取出数组第一个元素的地址,本质上是个指针。
printf("%d\n",sizeof(&a[0]+1));

那么同样的,&a[0]+1就代表指向第一个元素的指针向后走了一步,而这一步的大小是一个int,也就是指向了下标为1的元素。

现在看看结果,是否符合我们的推理。

 练习2:char arr[ ] zifu
char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));
	printf("%d\n", sizeof(arr + 0));
	printf("%d\n", sizeof(*arr));
	printf("%d\n", sizeof(arr[1]));
	printf("%d\n", sizeof(&arr));
	printf("%d\n", sizeof(&arr + 1));
	printf("%d\n", sizeof(&arr[0] + 1));

实际上,这题和上面那道题几乎一模一样。我就把解释写在代码里了哈。

char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));
//整个数组的大小,6
	printf("%d\n", sizeof(arr + 0));
//指向下标1的指针,4/8 ,8代表64位系统的结果
	printf("%d\n", sizeof(*arr));
//访问数组第一个元素,1
	printf("%d\n", sizeof(arr[1]));
//访问数组第一个元素,1
	printf("%d\n", sizeof(&arr));
//指向整个数组的指针,4/8
	printf("%d\n", sizeof(&arr + 1));
//指向整个数组的指针向后挪动一步,4/8
	printf("%d\n", sizeof(&arr[0] + 1));
//指向数组第一个元素的指针向后挪动一步,4/8

 对比
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));

实际上大体也是一样的,只不过我们换成了strlen,那就要特别注意\0。

char arr[] = { 'a','b','c','d','e','f' };
    printf("%d\n", strlen(arr));
//没有char arr[] = "abcdef";
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr+0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr+1));
printf("%d\n", sizeof(&arr[0]+1));,随机值
    printf("%d\n", strlen(arr+0));
//没有char arr[] = "abcdef";
    printf("%d\n", sizeof(arr));
//sizeof中的数组名代表整个数组,7(记得有隐藏的char arr[] = "abcdef";
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));元素)
    printf("%d\n", sizeof(arr+0));
//arr+0代表指针,指向第一个元素,4/8 (8是64位系统的结果)
    printf("%d\n", sizeof(*arr));
//代表字符串的第一个字符,类型char,1
    printf("%d\n", sizeof(arr[1]));
//代表字符串的第一个字符,类型char,1
    printf("%d\n", sizeof(&arr));
//指向整个char数组的指针,4/8
    printf("%d\n", sizeof(&arr+1));
//指向整个char数组的指针向后挪一步,4/8
    printf("%d\n", sizeof(&arr[0]+1));
//指向字符串第一个字符的指针向后挪一步,4/8,随机值
    printf("%d\n", strlen(*arr));
//*arr不是指针,不能计算
    printf("%d\n", strlen(arr[1]));
//arr[1]不是指针,不能计算
    printf("%d\n", strlen(&arr));
//没有char arr[] = "abcdef";
    printf("%d\n", strlen(arr));
//6
    printf("%d\n", strlen(arr+0));
//6
    printf("%d\n", strlen(*arr));
//不能计算,strlen要的是const char*指针类型的数据
    printf("%d\n", strlen(arr[1]));
//不能计算,strlen要的是const char*指针类型的数据
//虽然后面这两个语句不是const char*类型,但是可以把地址传过去
    printf("%d\n", strlen(&arr));
//6
    printf("%d\n", strlen(&arr+1));
//指向数组的指针向后挪动一步
//这一步的大小是一个数组的大小,打印结果随机值
    printf("%d\n", strlen(&arr[0]+1));
//指向数组中首元素的指针向后挪一步
//这步大小是一个char的大小,打印结果是5,随机值
    printf("%d\n", strlen(&arr+1));
//没有char *p = "abcdef";
printf("%d\n", sizeof(p));
printf("%d\n", sizeof(p+1));
printf("%d\n", sizeof(*p));
printf("%d\n", sizeof(p[0]));
printf("%d\n", sizeof(&p));
printf("%d\n", sizeof(&p+1));
printf("%d\n", sizeof(&p[0]+1));,随机值-6  因为是指向整个数组的指针向后挪一步
    printf("%d\n", strlen(&arr[0]+1));
//没有char *p = "abcdef";
    printf("%d\n", sizeof(p));
//p是个指针,4/8 (8是64位系统的结果)
    printf("%d\n", sizeof(p+1));
//p+1指向下一个字符c,4/8
    printf("%d\n", sizeof(*p));
//*p代表首字符字符a,1
    printf("%d\n", sizeof(p[0]));
//代表首字符字符a,1
    printf("%d\n", sizeof(&p));
//&p代表指针p的地址,4/8
    printf("%d\n", sizeof(&p+1));
//指向指针p的指针向后挪动一步,这一步大小是一个char*,4/8
    printf("%d\n", sizeof(&p[0]+1));
//指向首字符的指针向后挪动一步,这一步的大小是一个char,4/8,随机值-1 因为是指向一个元素的指针向后挪一步

很多警告,主要看类型不同的警告,正如我们所说,*arr和arr[0]是不能计算的,

在注释掉后,计算的结果就像我们注释里说的。

 练习3:char arr[ ]
char *p = "abcdef";
printf("%d\n", strlen(p));
printf("%d\n", strlen(p+1));
printf("%d\n", strlen(*p));
printf("%d\n", strlen(p[0]));
printf("%d\n", strlen(&p));
printf("%d\n", strlen(&p+1));
printf("%d\n", strlen(&p[0]+1));

要知道的是,储存字符串当中的字符数组名是第一个字符的地址。

char *p = "abcdef";
    printf("%d\n", strlen(p));
//6
    printf("%d\n", strlen(p+1));
//5
    printf("%d\n", strlen(*p));
    printf("%d\n", strlen(p[0]));
//上面的*p和p[0]是char类型,不能计算
    printf("%d\n", strlen(&p));
//p此时是个指向首元素的指针,而&p代表指向p的指针
//那么&p中保存的是p的地址,int a[3][4] = {0};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a[0][0]));
printf("%d\n",sizeof(a[0]));
printf("%d\n",sizeof(a[0]+1));
printf("%d\n",sizeof(*(a[0]+1)));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(*(a+1)));
printf("%d\n",sizeof(&a[0]+1));
printf("%d\n",sizeof(*(&a[0]+1)));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a[3]));位置不确定,计算值会是个随机值
    printf("%d\n", strlen(&p+1));
//&p是个指向p的指针,类型char**,向后挪动一步的大小是char*
//char*是个指针,指针的大小是4/8
//所以结果是随机值-4/8 (8是64位系统的)
    printf("%d\n", strlen(&p[0]+1));
//5

 对比\0
int a[3][4] = {0};
    printf("%d\n",sizeof(a));
//sizeof中的a代表整个数组,12*4=48
    printf("%d\n",sizeof(a[0][0]));
//第一行第一个元素,4

注意strlen是找\0来结束运算的,而字符串后会有一个隐藏的\0

printf("%d\n",sizeof(a[0]));
//实际上a[0]是第一行一维数组的数组名,那么
//sizeof中的数组名代表整个数组
//而数组名a[0]其实就是个指针,指向4个int
//二维数组中第一行的大小,第一行4个元素
//结果是4*4=16

 练习4:char*p
printf("%d\n",sizeof(a[0]+1));
//a[0]的类型是int[4],作为指针就是int*,指向int
//向后加一的步长是int,指向第一行下标为1的元素
//输出结果是4/8

第一句的意思是,char*指针p指向后面字符串首元素a的地址。

printf("%d\n",sizeof(*(a[0]+1)));
//a[0]+1代表指向 第一行下标为1的元素 的指针
//解引用后代表访问这个元素
//元素类型int,那么结果为4

对比 
printf("%d\n",sizeof(a+1));
//a的类型是int[3][4]
//作为指针的话是一个一维数组int[4]*
//+1的步长是4个int,那么a+1就代表第二行的地址
//结果是4/8
    printf("%d\n",sizeof(*(a+1)));
//*(a+1)=a[1],是第二行的一维数组名
//sizeof中的数组名代表整个数组的大小
//此时的a[1]是第二行,有4个元素
//那就是4个int,16byte

依旧注意p是个指向a的指针,strlen主要看\0的位置。

printf("%d\n",sizeof(&a[0]+1));
//a[0]代表第一行的数组名
//&a[0]就代表指向第一行的指针
//此时的+1步长是 一行数组 的大小
//指针指向第二行的一维数组
//结果4/8
    printf("%d\n",sizeof(*(&a[0]+1)));
//&a[0]+1代表 指向第二行一维数组的指针
//对指针解引用就是a[1]
//结果4*4=16

 练习5:二维数组
printf("%d\n",sizeof(*a));
//a的类型是int[3][4],作为指针是int[4]*
//解引用后是int[4]
//那么*a就代表第一行的数组名
//实际上*a=*(a+0)=a[0]
//那么结果是4*4=16
    printf("%d\n",sizeof(a[3]));
//二维数组一共三行,最大是a[2]
//sizeof中的a[3]结果是这个表达式的类型大小
//所以不涉及越界访问的问题
//直接就是一行元素的大小,16

二维数组的话要明白,二维数组的数组名在作为指针的情况下是个一维数组指针。

int main()
{
    int a[5] = { 1, 2, 3, 4, 5 };
    int *ptr = (int *)(&a + 1);
    printf( "%d,%d", *(a + 1), *(ptr - 1));
    return 0; }
//由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
 int Num;
 char *pcName;
 short sDate;
 char cha[2];
 short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
 printf("%p\n", p + 0x1);
 printf("%p\n", (unsigned long)p + 0x1);
 printf("%p\n", (unsigned int*)p + 0x1);
 return 0; }
还不完全一样
我们得看指针类型,才能确定相邻的指针会差多少字节。
非要说有的话,也只有在按照不同数据进行分配空间后才算是有了类型。
而地址在有了类型后,也就是指针了。
以字节的方式来理解地址和指针

 练习6:判断程序结果
//假设p 的值为0x100000。 如下表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
     printf("%p\n", p + 0x1);
//p+1的步长是20字节 - 十六进制是14
//0x100014
     printf("%p\n", (unsigned long)p + 0x1);
//注意是把p这个结构体指针转换成一个整型了
//所以是加了十六进制的数字1
//结果是0x100001
     printf("%p\n", (unsigned int*)p + 0x1);
//p+1的步长是4或者8字节(32位/64位系统)
//0x100004

首先看*(a+1),a+1代表下标为1的地址,*(a+1)=a[1],结果是2

*(ptr - 1),先分析ptr接收了谁的地址

&a+1 代表 &a是整个数组的地址,+1则是指向a后面一个数组大小的地址

int*强制类型转换后,只能访问一个int大小的地址并存到ptr中,

此时ptr指向a[4]这个元素后面的地址,ptr是int*指针

那么ptr-1的步长是一个int,ptr-1指向a[4],解引用后就是5

output:2,5

练习7:第一眼没看出来的考点(重点看)
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; }

说实话,这个题我做过一遍,第二遍还是没看出来在干嘛

然后我思考了一下原因,就在于我被0x1迷惑了。我一看到0x1完全忘了指针p的作用,只想着地址加1是怎么加啊?

我一直在想地址加1难道不是纯粹的加1吗?这题为啥要这么写?

还真不是!!!

***指针和地址的新理解***

因为你要知道的是其实指针就是地址,地址就是指针

但是还有一个问题,那为什么普通的整型+1就是数值上的+1而不是字节上的+1呢?,因为如果单纯的说地址,那地址是一个储存单元一个地址,相邻的地址会相差1个字节,但是相邻的指针会相差一个字节吗?

那还真不是,地址之间相邻差1byte,所以+1 的步长就是1byte

那么话说回来了,地址会有类型吗? 那还真没有,如果 (int)a (int)a+1
但是并没有看到说对地址分类的说法,所以我建议是 ,也就是说,无论是地址还是字节,在分析+1步长的时候,得 看看它指向的类型占了几个字节来判断。
我觉得这个理解超级对!!以后的我,常回来看看,有新的理解一定要及时改动! 分析题 现在来分析题目,其实题目考察的就是指针类型对于+1步长的影响。
 
 练习8:%x?int+1?(又被击败) 
 
 
 

先分析ptr1指针接收到的地址是谁

&a是整个数组的大小,+1后指向a数组后面 一个数组大小 的空间

int*强制类型转换后就只能访问一个int大小的空间,把这个空间(也就是a[3]后面的int空间)存到了ptr1中

对于非指针+1的步长理解(地址神奇~)

接着是ptr2,首先a是指向数组首元素的,但是被强制转换为了int类型的数据,那么(int)a+1的步长会是多少呢?

int*+1会跳过4个字节,因为指针指向的int占4byte

char*+1跳过1个字节,因为指针指向的char占1byte

类似的,

我们的int+1是多大呢?难道是单纯的1+1=2吗?

我的思考过程是这样

        首先a是数组名,指向首元素的地址,在int强制转换后,是把一个地址强制转换为了int类型。按理说,int+1应该是按照一个int大小来向后挪动的啊!但是这样的话,int*指针又该怎么解释步长是一个int大小呢?

        关于这个问题,其实上个练习也涉及到了。需要知道的是我们在转换为int的时候,是把地址转换为了int类型的数据,也就是说此时的地址不是指向某个数据的指针,而是单纯的数字,其实就是给地址编号加了个数字1,而。

那这时候我们知道了(int)a+1就是a内存中第二个字节的地址,此时把这个地址转换为int*指针,而一个int*指针可以访问4个字节。我们按照小端储存来看看此时的ptr储存的是哪些。

00000002那么 p 指向的就是 a 数组中第一行的前四个元素。a作为指针使用的时候就是int[5]*指针*(a+i)=a[ i ]
010000
00(我加了#,因为没有0x看着别扭)int main() {    int a[3][2] = { (0, 1), (2, 3), (4, 5) };    int *p;    p = a[0];    printf( "%d", p[0]); return 0; }int a[3][2] = { (0, 1), (2, 3), (4, 5) }; int a[3][2] = { 1, 3, 5 };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; }

上面标红的就是ptr指向的内存单元。

那么打印的结果

ptr[-1]是指向ptr指针前面一个int空间的数据,这个空间的数据是a[3]。

按照小端储存的内存中是(低地址)04 00 00 00(高地址)

因为我们把数据存放到内存中按照小端储存,那就是数据的低位放到低地址,高位放到高地址,所以打印出来就是(高位)00 00 00 04 (低位)(省略前面的0)

*ptr则是解引用ptr指针,去通过这个地址访问,访问到的是(低地址)00 00 00 02(高地址)

因为我们把数据存放到内存中按照小端储存,那就是数据的低位放到低地址,高位放到高地址,打印出来的就是(高位)02 00 00 00(低位)(省略前面的0)

看结果a[4]的意思就是a向后挪动了4个步长,每个步长是int[5],那么此时的a[4]已经跳过了4*5=20个int元素

 地址转换为int+1,就是单纯的数字+1啦,实际上就是跳过了一个byte的地址。

练习9:有点陷阱
p[4]就代表p指针向后挪动了4个步长,每个步长是int[4],一共跳过了4*4=16个int元素

这题其实挺搞笑的,要是看走眼就无了

你要注意的是后面是逗号表达式,所以第一行的语句实际上就是

总结:二维数组

a[0]代表二维数组第一行,也就是1和3两个元素。

我们的int*p指向指向了a[0]这个一维数组,也就是说我们指向了第一行int[2]大小的首元素地址,因为p是int*指针,便一次指向一个int大小的空间,也就是指向首元素1。

而打印中的p[0]实际上就等价于*(p+0),也就是首元素1。

练习10:二维数组指针运用、指针运算、%p打印整型
p [4][2]=* ( * ( p + 4 ) + 2 );   

a的类型是int[5][5]二维数组,我们的p是int[4]*类型的指针,指向int[4]。

此时我们把p=a,而 a 没有被&也没有在sizeof当中,所以是数组首元素的地址,也就是第一行

因为地址无所谓正负,所以%p认为内存中存的就是一个地址,直接按照内存中的补码输出。

接着分析&p[4][2]和&a[4][2]什么意思

        首先我们知道p是指向a数组中第一行前四个元素的指针,是一个int[4]*的指针。

        a我们说是int[5][5]类型,而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; },那么此时a+1的步长就会是一个int[5]的大小,而我们又知道ptr1指向的就是aa数组最后一个元素后面的一个int大小的空间,代表a这个指针向后挪动了 i 步并访问这个空间的元素。那么int main() { char *a[] = {"work","at","alibaba"}; char**pa = a; pa++; printf("%s\n", *pa); return 0; }。此时的a[4]是一个int[5]类型的数组,把a[4]看做这个int[5]的数组名,那么a[4]数组名在作为指针使用的时候就是int*指针,那么a[4][2]就代表访问了a[4]数组中下标为2的元素。

        同样的道理,p是一个int[4]*的指针,我们又知道p的地址是a数组首元素的地址,指向a数组第一行的前四个元素,那么它+1的步长是int[4],"work" 字符串  是一堆char类型数据的集合,也就是char[5]数组,此时的p[4]就是一个int[4]类型的数组名,而p[4]数组名在作为指针使用的时候就是int*指针,p[4][2]就代表访问了p[4]数组中下标为2的元素。

“work”字符串就相当于char[5]的数组名,

数组名在运用的时候会变为指针,也就是会从char[5]数组变为char*指针(注:sizeof和&中的数组名除外)

我画个图,配合来看。

此时p[4][2]和a[4][2]的地址相4个int,也就是16个byte。

要知道 &p[4][2] - &a[4][2]是指针之间的元素个数,又因为p的地址比a的地址低,那么%d的形式打印出来就是-4,而%p的话则需要把-4的补码写出来,按照16进制数字进行输出。

那么为什么%p就不用像%d一样进行原码转换输出,而是直接使用补码呢?

数组名“work”作为指针的时候是指向首元素的,也就是指向w这个字符

这里我们演示一下

要达到,一眼能看出来代码在说什么的水平,这题就算OK了。

放一下结果

练习11:二维数组
当我们在char*[ ]数组中存放“work”字符串的时候,是存放了字符w的地址。

这题比较简单。

首先&aa是取出来了整个二维数组大小的地址,向后+1的步长是一个二维数组的大小,转换为int*指针便只能访问一个int,那么如图。

*(ptr-1)就是指ptr指针向前挪了一个int大小,正好指向数组最后一个元素,然后访问住户,那么结果就是10。

同样的道理,

aa是int[2][5]数组,在作为指针运用的时候是一个int[5]*指针,指向第一行的一维int[5]数组,+1的步长就是指向第二行的一维数组int[5],加上*解引用代表访问这个数组,那么也就相当于aa[1]。

实际上也可直接利用*(a+i)=a[ i ],上面只是详细解释了一下。

aa[1]的类型是int[5],此时被强制转化为int*指针,那便只能指向一个int大小的空间,也就是指向a[1][0],那么ptr2就是指向了a[1][0]。

*(ptr2-1)就是向前挪了一个int大小,指向a[0][4]元素,*代表访问元素,结果就是5。

如图。 

练习12:char*a[ ]
 char**pa = a;

        首先我们要理解char*a[ ]初始化意味着什么,要知道数组中存放的是char*类型的数据,而我们初始化的时候放的是字符串,那么字符串又意味着什么?

        可以看到  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; }。那么 char *c[] = {"ENTER","NEW","POINT","FIRST"}; char**cp[] = {c+3,c+2,c+1,c};而我们又知道,char**[ ]则代表我们存放的数据是char**这个二级指针数据。所以实际上我们存放的就是char*类型的数据,还需要知道的是数组名c存放了首元素char*的地址。

        也就是说,实际上c,c+1,c+2,c+3就是char**类型的数据

c,c+1,c+2,c+3便分别指向c数组中下标为0,1,2,3,4,的元素

而这些元素的类型都是char*类型,

上面这个语句的意思是,现在我们用pa指针指向a数组中首元素的地址,指向的数据类型是char*。

也就是说指向第一个char*。如图。

         pa++是指向第二个char*的指针,现在对指针进行*解引用,也就是访问这个char*元素,而我们在打印的时候需要传递的就是char*数据,这个char*指向字符串“at”当中'a'的地址,所以打印的结果是at。

练习13:char***之终极练习
c,c+1,c+2,c+3这四个char**元素,分别存放了下标为0,1,2,3,4,的char*元素的地址。(指向谁,存放谁的地址)

 首先梳理第一句的意思,我们在上面已经分析过char*[ ]当中存放字符串意味着什么,这里便只放图了。

 存放了四个字符串首元素的地址。

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);,那么我们又知道c是char*[4]数组,是一个数组名,而数组名在运用的时候就会变为指向首元素的指针,也就是从char*[4]类型变为char**指针,代表指向char*数据的指针,也就是 *++cpp=c+2。所以 printf("%s\n", **++cpp); printf("%s\n", *--*++cpp+3); printf("%s\n", *cpp[-2]+3); printf("%s\n", cpp[-1][-1]+1);,又因为c是指向数组首元素的,那么c+1,c+2,c+3便是指针c向后移动的步数,c是一个char**指针,移动的步长是char*,那么此时的 第二句的cpp是上一句的++cpp,是指向c+2的。总体过程是:*--*++cpp + 3 = *--(c+1)+ 3 = *c+3 = "ENTER" + 3 = "ER"也就是 "ENTER"(字符串数组名,实际上是首元素'E'的地址,类型char*)

 

接着是第三句。

 printf("%s\n", **++cpp);
 printf("%s\n", *--*++cpp+3);
 printf("%s\n", *cpp[-2]+3);
 printf("%s\n", cpp[-1][-1]+1);
 char***是指向char**数据的指针,此时的cp是指向首元素c+3这个char**的char***指针,把cp的地址交给cpp,也就代表cpp和cp一样,是指向首元素c+3这个char**的char***指针,也就是存放了c+3这个char**的地址。
*cpp[-2]+3 = *(*(cpp-2))+3 = *(c+3)+3 = "FIRST" + 3 = "ST"

先分析第一句,++cpp代表cpp向后挪动了一步,这一步的大小是一个char**。++cpp的类型是char***,对++cpp进行解引用,*++cpp就是char**类型,也就是在访问++cpp所指向的对象,此时

++cpp指向c+2,那么"FIRST"(字符串数组名,实际上是首元素'F'的地址,类型char*)

再进行解引用的话,就 相当于对c+2进行解引用,c+2指向的对象是  "POINT" 的 首字符 'P' 地址,对c+2进行解引用代表访问  c+2所指向的对象,也就是访问"POINT" 的 首字符 'P' 地址,此时把这个地址用%s的形式打印, 那么便是POINT。 如图。

 现在分析第二句。

 printf("%s\n", **++cpp);
 printf("%s\n", *--*++cpp+3);
 printf("%s\n", *cpp[-2]+3);
 printf("%s\n", cpp[-1][-1]+1);

 我们需要知道的是前置后置++和--都会对数据本身造成影响,也就是说cpp[-1][-1]+1 = *(*(cpp-1)-1)+ 1 = *((c+2)-1)+1 = *(c+1)+1 = "NEW"+1=其次,我们要先运算*--*++cpp+3中的++cpp。那么++cpp指向c+1,解引用的话就是访问++cpp指向的对象,++cpp指向c+1,那么*++cpp=c+1,然后我们再对c+1进行前置--,那么c+1就会变为c。再对c进行解引用,就是在访问c所指向的对象,c指向ENTER字符串中E的地址,再对这个地址+3就意味着,从字符E向后走了3步,每步大小是一个字符char,那么打印结果是ER。

"EW" "NEW"(字符串数组名,实际上是首元素'N'的地址,类型char*)

 分析第三句

直接写出整体运算过程
此时的cpp指向cp数组中第三个元素,(不再细说,道理想通) 分析最后一句
 
 

因为上一句并不是使用的前置后置++或者--,所以cpp还是指向cp数组中第三个元素。

[+++][+++]

[+++]

 这题达到能[+++]的水平就可以了。

练习到此,后续还会有练习,不过这算是比较深入的练习了,如果我没解释清楚,可以看我之前对于指针文章的解释,或者直接私信我,很乐意解答,可惜没人看哈哈哈

)
File: /www/wwwroot/outofmemory.cn/tmp/route_read.php, Line: 126, InsideLink()
File: /www/wwwroot/outofmemory.cn/tmp/index.inc.php, Line: 166, include(/www/wwwroot/outofmemory.cn/tmp/route_read.php)
File: /www/wwwroot/outofmemory.cn/index.php, Line: 30, include(/www/wwwroot/outofmemory.cn/tmp/index.inc.php)
Error[8]: Undefined offset: 124, File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 121
File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 473, decode(

上一篇:从0开始学c语言-29-数据储存练习、筑基期练习、两步翻转_阿秋的阿秋不是阿秋的博客-CSDN博客

应有的基础:

从0开始学c语言-28-qsort函数、 数组和指针参数、函数指针数组(转移表)、回调函数_阿秋的阿秋不是阿秋的博客-CSDN博客

从0开始学c语言-27-字符指针,指针数组和数组指针_阿秋的阿秋不是阿秋的博客-CSDN博客

目录

练习1:int arr[ ]

 练习2:char arr[ ] zifu

 对比

 练习3:char arr[ ]

 对比\0

 练习4:char*p

对比 

 练习5:二维数组

 练习6:判断程序结果

练习7:第一眼没看出来的考点(重点看)

***指针和地址的新理解***

分析题

练习8:%x?int+1?(又被击败)

对于非指针+1的步长理解(地址神奇~)

练习9:有点陷阱

练习10:二维数组指针运用、指针运算、%p打印整型

练习11:二维数组

练习12:char*a[ ]

练习13:char***之终极练习


练习1:int arr[ ]

应有的知识:

数组名的意义: 1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。 2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。 3. 除此之外所有的数组名都表示首元素的地址。
//一维数组
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a+0));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(a[1]));
printf("%d\n",sizeof(&a));
printf("%d\n",sizeof(*&a));
printf("%d\n",sizeof(&a+1));
printf("%d\n",sizeof(&a[0]));
printf("%d\n",sizeof(&a[0]+1));

请思考每个printf函数输出的结果是多少。

开始讲

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));

a单纯的数组名实际上就是数组首元素的地址,那么地址的大小在32位系统上是4byte,在64位系统上是8byte大小。但是呢,sizeof当中的数组名则代表整个数组,这一点要特别注意,那么a数组的大小就是4个int的大小,也就是16byte。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a+0));

实际上地址就是指针,那么a+0就代表a这个指针向后挪动了0步。且a本身指向数组首元素,+0表示一步没有挪动。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(*a));

我们知道a是指针,*a就是对指针进行解引用,a指向数组首元素,那么*a就是在访问数组首元素,而数组首元素的类型是int,int的大小是4byte。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a+1));

a+0和a+1都是指针,只不过一个指向首元素,一个指向下标为1的元素。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a[1]));

a[1]代表数组下标为1的元素,而这个元素的类型是int,那么就是4byte。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(&a));

我们早在前面说过,

1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。 2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
那么现在是sizeof和&数组名组合到一起,这时候,sizeof当中的&数组名,则是计算的 【指向整个数组的指针】的大小。
printf("%d\n",sizeof(*&a));

现在在&a前面加上了*, 而*&a=a,a又在sizeof当中,那就代表我们访问了整个数组,应该是16byte。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(&a+1));

&a代表指向整个数组的指针,那么+1则会让指针向后挪动一步,这一步的大小是整个数组的大小,我们计算的还是 指针的大小。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(&a[0]));

&a[0]是什么意思?

如果讲结合性的话,我还不太熟练,只能放图监视给你看

 也就是说&a[0]=&(a[0])

意思是取出数组第一个元素的地址,本质上是个指针。
printf("%d\n",sizeof(&a[0]+1));

那么同样的,&a[0]+1就代表指向第一个元素的指针向后走了一步,而这一步的大小是一个int,也就是指向了下标为1的元素。

现在看看结果,是否符合我们的推理。

 练习2:char arr[ ] zifu
char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));
	printf("%d\n", sizeof(arr + 0));
	printf("%d\n", sizeof(*arr));
	printf("%d\n", sizeof(arr[1]));
	printf("%d\n", sizeof(&arr));
	printf("%d\n", sizeof(&arr + 1));
	printf("%d\n", sizeof(&arr[0] + 1));

实际上,这题和上面那道题几乎一模一样。我就把解释写在代码里了哈。

char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));
//整个数组的大小,6
	printf("%d\n", sizeof(arr + 0));
//指向下标1的指针,4/8 ,8代表64位系统的结果
	printf("%d\n", sizeof(*arr));
//访问数组第一个元素,1
	printf("%d\n", sizeof(arr[1]));
//访问数组第一个元素,1
	printf("%d\n", sizeof(&arr));
//指向整个数组的指针,4/8
	printf("%d\n", sizeof(&arr + 1));
//指向整个数组的指针向后挪动一步,4/8
	printf("%d\n", sizeof(&arr[0] + 1));
//指向数组第一个元素的指针向后挪动一步,4/8

 对比
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));

实际上大体也是一样的,只不过我们换成了strlen,那就要特别注意\0。

char arr[] = { 'a','b','c','d','e','f' };
    printf("%d\n", strlen(arr));
//没有char arr[] = "abcdef";
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr+0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr+1));
printf("%d\n", sizeof(&arr[0]+1));,随机值
    printf("%d\n", strlen(arr+0));
//没有char arr[] = "abcdef";
    printf("%d\n", sizeof(arr));
//sizeof中的数组名代表整个数组,7(记得有隐藏的char arr[] = "abcdef";
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));元素)
    printf("%d\n", sizeof(arr+0));
//arr+0代表指针,指向第一个元素,4/8 (8是64位系统的结果)
    printf("%d\n", sizeof(*arr));
//代表字符串的第一个字符,类型char,1
    printf("%d\n", sizeof(arr[1]));
//代表字符串的第一个字符,类型char,1
    printf("%d\n", sizeof(&arr));
//指向整个char数组的指针,4/8
    printf("%d\n", sizeof(&arr+1));
//指向整个char数组的指针向后挪一步,4/8
    printf("%d\n", sizeof(&arr[0]+1));
//指向字符串第一个字符的指针向后挪一步,4/8,随机值
    printf("%d\n", strlen(*arr));
//*arr不是指针,不能计算
    printf("%d\n", strlen(arr[1]));
//arr[1]不是指针,不能计算
    printf("%d\n", strlen(&arr));
//没有char arr[] = "abcdef";
    printf("%d\n", strlen(arr));
//6
    printf("%d\n", strlen(arr+0));
//6
    printf("%d\n", strlen(*arr));
//不能计算,strlen要的是const char*指针类型的数据
    printf("%d\n", strlen(arr[1]));
//不能计算,strlen要的是const char*指针类型的数据
//虽然后面这两个语句不是const char*类型,但是可以把地址传过去
    printf("%d\n", strlen(&arr));
//6
    printf("%d\n", strlen(&arr+1));
//指向数组的指针向后挪动一步
//这一步的大小是一个数组的大小,打印结果随机值
    printf("%d\n", strlen(&arr[0]+1));
//指向数组中首元素的指针向后挪一步
//这步大小是一个char的大小,打印结果是5,随机值
    printf("%d\n", strlen(&arr+1));
//没有char *p = "abcdef";
printf("%d\n", sizeof(p));
printf("%d\n", sizeof(p+1));
printf("%d\n", sizeof(*p));
printf("%d\n", sizeof(p[0]));
printf("%d\n", sizeof(&p));
printf("%d\n", sizeof(&p+1));
printf("%d\n", sizeof(&p[0]+1));,随机值-6  因为是指向整个数组的指针向后挪一步
    printf("%d\n", strlen(&arr[0]+1));
//没有char *p = "abcdef";
    printf("%d\n", sizeof(p));
//p是个指针,4/8 (8是64位系统的结果)
    printf("%d\n", sizeof(p+1));
//p+1指向下一个字符c,4/8
    printf("%d\n", sizeof(*p));
//*p代表首字符字符a,1
    printf("%d\n", sizeof(p[0]));
//代表首字符字符a,1
    printf("%d\n", sizeof(&p));
//&p代表指针p的地址,4/8
    printf("%d\n", sizeof(&p+1));
//指向指针p的指针向后挪动一步,这一步大小是一个char*,4/8
    printf("%d\n", sizeof(&p[0]+1));
//指向首字符的指针向后挪动一步,这一步的大小是一个char,4/8,随机值-1 因为是指向一个元素的指针向后挪一步

很多警告,主要看类型不同的警告,正如我们所说,*arr和arr[0]是不能计算的,

在注释掉后,计算的结果就像我们注释里说的。

 练习3:char arr[ ]
char *p = "abcdef";
printf("%d\n", strlen(p));
printf("%d\n", strlen(p+1));
printf("%d\n", strlen(*p));
printf("%d\n", strlen(p[0]));
printf("%d\n", strlen(&p));
printf("%d\n", strlen(&p+1));
printf("%d\n", strlen(&p[0]+1));

要知道的是,储存字符串当中的字符数组名是第一个字符的地址。

char *p = "abcdef";
    printf("%d\n", strlen(p));
//6
    printf("%d\n", strlen(p+1));
//5
    printf("%d\n", strlen(*p));
    printf("%d\n", strlen(p[0]));
//上面的*p和p[0]是char类型,不能计算
    printf("%d\n", strlen(&p));
//p此时是个指向首元素的指针,而&p代表指向p的指针
//那么&p中保存的是p的地址,int a[3][4] = {0};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a[0][0]));
printf("%d\n",sizeof(a[0]));
printf("%d\n",sizeof(a[0]+1));
printf("%d\n",sizeof(*(a[0]+1)));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(*(a+1)));
printf("%d\n",sizeof(&a[0]+1));
printf("%d\n",sizeof(*(&a[0]+1)));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a[3]));位置不确定,计算值会是个随机值
    printf("%d\n", strlen(&p+1));
//&p是个指向p的指针,类型char**,向后挪动一步的大小是char*
//char*是个指针,指针的大小是4/8
//所以结果是随机值-4/8 (8是64位系统的)
    printf("%d\n", strlen(&p[0]+1));
//5

 对比\0
int a[3][4] = {0};
    printf("%d\n",sizeof(a));
//sizeof中的a代表整个数组,12*4=48
    printf("%d\n",sizeof(a[0][0]));
//第一行第一个元素,4

注意strlen是找\0来结束运算的,而字符串后会有一个隐藏的\0

printf("%d\n",sizeof(a[0]));
//实际上a[0]是第一行一维数组的数组名,那么
//sizeof中的数组名代表整个数组
//而数组名a[0]其实就是个指针,指向4个int
//二维数组中第一行的大小,第一行4个元素
//结果是4*4=16

 练习4:char*p
printf("%d\n",sizeof(a[0]+1));
//a[0]的类型是int[4],作为指针就是int*,指向int
//向后加一的步长是int,指向第一行下标为1的元素
//输出结果是4/8

第一句的意思是,char*指针p指向后面字符串首元素a的地址。

printf("%d\n",sizeof(*(a[0]+1)));
//a[0]+1代表指向 第一行下标为1的元素 的指针
//解引用后代表访问这个元素
//元素类型int,那么结果为4

对比 
printf("%d\n",sizeof(a+1));
//a的类型是int[3][4]
//作为指针的话是一个一维数组int[4]*
//+1的步长是4个int,那么a+1就代表第二行的地址
//结果是4/8
    printf("%d\n",sizeof(*(a+1)));
//*(a+1)=a[1],是第二行的一维数组名
//sizeof中的数组名代表整个数组的大小
//此时的a[1]是第二行,有4个元素
//那就是4个int,16byte

依旧注意p是个指向a的指针,strlen主要看\0的位置。

printf("%d\n",sizeof(&a[0]+1));
//a[0]代表第一行的数组名
//&a[0]就代表指向第一行的指针
//此时的+1步长是 一行数组 的大小
//指针指向第二行的一维数组
//结果4/8
    printf("%d\n",sizeof(*(&a[0]+1)));
//&a[0]+1代表 指向第二行一维数组的指针
//对指针解引用就是a[1]
//结果4*4=16

 练习5:二维数组
printf("%d\n",sizeof(*a));
//a的类型是int[3][4],作为指针是int[4]*
//解引用后是int[4]
//那么*a就代表第一行的数组名
//实际上*a=*(a+0)=a[0]
//那么结果是4*4=16
    printf("%d\n",sizeof(a[3]));
//二维数组一共三行,最大是a[2]
//sizeof中的a[3]结果是这个表达式的类型大小
//所以不涉及越界访问的问题
//直接就是一行元素的大小,16

二维数组的话要明白,二维数组的数组名在作为指针的情况下是个一维数组指针。

int main()
{
    int a[5] = { 1, 2, 3, 4, 5 };
    int *ptr = (int *)(&a + 1);
    printf( "%d,%d", *(a + 1), *(ptr - 1));
    return 0; }
//由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
 int Num;
 char *pcName;
 short sDate;
 char cha[2];
 short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
 printf("%p\n", p + 0x1);
 printf("%p\n", (unsigned long)p + 0x1);
 printf("%p\n", (unsigned int*)p + 0x1);
 return 0; }
还不完全一样
我们得看指针类型,才能确定相邻的指针会差多少字节。
非要说有的话,也只有在按照不同数据进行分配空间后才算是有了类型。
而地址在有了类型后,也就是指针了。
以字节的方式来理解地址和指针

 练习6:判断程序结果
//假设p 的值为0x100000。 如下表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
     printf("%p\n", p + 0x1);
//p+1的步长是20字节 - 十六进制是14
//0x100014
     printf("%p\n", (unsigned long)p + 0x1);
//注意是把p这个结构体指针转换成一个整型了
//所以是加了十六进制的数字1
//结果是0x100001
     printf("%p\n", (unsigned int*)p + 0x1);
//p+1的步长是4或者8字节(32位/64位系统)
//0x100004

首先看*(a+1),a+1代表下标为1的地址,*(a+1)=a[1],结果是2

*(ptr - 1),先分析ptr接收了谁的地址

&a+1 代表 &a是整个数组的地址,+1则是指向a后面一个数组大小的地址

int*强制类型转换后,只能访问一个int大小的地址并存到ptr中,

此时ptr指向a[4]这个元素后面的地址,ptr是int*指针

那么ptr-1的步长是一个int,ptr-1指向a[4],解引用后就是5

output:2,5

练习7:第一眼没看出来的考点(重点看)
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; }

说实话,这个题我做过一遍,第二遍还是没看出来在干嘛

然后我思考了一下原因,就在于我被0x1迷惑了。我一看到0x1完全忘了指针p的作用,只想着地址加1是怎么加啊?

我一直在想地址加1难道不是纯粹的加1吗?这题为啥要这么写?

还真不是!!!

***指针和地址的新理解***

因为你要知道的是其实指针就是地址,地址就是指针

但是还有一个问题,那为什么普通的整型+1就是数值上的+1而不是字节上的+1呢?,因为如果单纯的说地址,那地址是一个储存单元一个地址,相邻的地址会相差1个字节,但是相邻的指针会相差一个字节吗?

那还真不是,地址之间相邻差1byte,所以+1 的步长就是1byte

那么话说回来了,地址会有类型吗? 那还真没有,如果 (int)a (int)a+1
但是并没有看到说对地址分类的说法,所以我建议是 ,也就是说,无论是地址还是字节,在分析+1步长的时候,得 看看它指向的类型占了几个字节来判断。
我觉得这个理解超级对!!以后的我,常回来看看,有新的理解一定要及时改动! 分析题 现在来分析题目,其实题目考察的就是指针类型对于+1步长的影响。
 
 练习8:%x?int+1?(又被击败) 
 
 
 

先分析ptr1指针接收到的地址是谁

&a是整个数组的大小,+1后指向a数组后面 一个数组大小 的空间

int*强制类型转换后就只能访问一个int大小的空间,把这个空间(也就是a[3]后面的int空间)存到了ptr1中

对于非指针+1的步长理解(地址神奇~)

接着是ptr2,首先a是指向数组首元素的,但是被强制转换为了int类型的数据,那么(int)a+1的步长会是多少呢?

int*+1会跳过4个字节,因为指针指向的int占4byte

char*+1跳过1个字节,因为指针指向的char占1byte

类似的,

我们的int+1是多大呢?难道是单纯的1+1=2吗?

我的思考过程是这样

        首先a是数组名,指向首元素的地址,在int强制转换后,是把一个地址强制转换为了int类型。按理说,int+1应该是按照一个int大小来向后挪动的啊!但是这样的话,int*指针又该怎么解释步长是一个int大小呢?

        关于这个问题,其实上个练习也涉及到了。需要知道的是我们在转换为int的时候,是把地址转换为了int类型的数据,也就是说此时的地址不是指向某个数据的指针,而是单纯的数字,其实就是给地址编号加了个数字1,而。

那这时候我们知道了(int)a+1就是a内存中第二个字节的地址,此时把这个地址转换为int*指针,而一个int*指针可以访问4个字节。我们按照小端储存来看看此时的ptr储存的是哪些。

00000002那么 p 指向的就是 a 数组中第一行的前四个元素。a作为指针使用的时候就是int[5]*指针*(a+i)=a[ i ]
010000
00(我加了#,因为没有0x看着别扭)int main() {    int a[3][2] = { (0, 1), (2, 3), (4, 5) };    int *p;    p = a[0];    printf( "%d", p[0]); return 0; }int a[3][2] = { (0, 1), (2, 3), (4, 5) }; int a[3][2] = { 1, 3, 5 };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; }

上面标红的就是ptr指向的内存单元。

那么打印的结果

ptr[-1]是指向ptr指针前面一个int空间的数据,这个空间的数据是a[3]。

按照小端储存的内存中是(低地址)04 00 00 00(高地址)

因为我们把数据存放到内存中按照小端储存,那就是数据的低位放到低地址,高位放到高地址,所以打印出来就是(高位)00 00 00 04 (低位)(省略前面的0)

*ptr则是解引用ptr指针,去通过这个地址访问,访问到的是(低地址)00 00 00 02(高地址)

因为我们把数据存放到内存中按照小端储存,那就是数据的低位放到低地址,高位放到高地址,打印出来的就是(高位)02 00 00 00(低位)(省略前面的0)

看结果a[4]的意思就是a向后挪动了4个步长,每个步长是int[5],那么此时的a[4]已经跳过了4*5=20个int元素

 地址转换为int+1,就是单纯的数字+1啦,实际上就是跳过了一个byte的地址。

练习9:有点陷阱
p[4]就代表p指针向后挪动了4个步长,每个步长是int[4],一共跳过了4*4=16个int元素

这题其实挺搞笑的,要是看走眼就无了

你要注意的是后面是逗号表达式,所以第一行的语句实际上就是

总结:二维数组

a[0]代表二维数组第一行,也就是1和3两个元素。

我们的int*p指向指向了a[0]这个一维数组,也就是说我们指向了第一行int[2]大小的首元素地址,因为p是int*指针,便一次指向一个int大小的空间,也就是指向首元素1。

而打印中的p[0]实际上就等价于*(p+0),也就是首元素1。

练习10:二维数组指针运用、指针运算、%p打印整型
p [4][2]=* ( * ( p + 4 ) + 2 );   

a的类型是int[5][5]二维数组,我们的p是int[4]*类型的指针,指向int[4]。

此时我们把p=a,而 a 没有被&也没有在sizeof当中,所以是数组首元素的地址,也就是第一行

因为地址无所谓正负,所以%p认为内存中存的就是一个地址,直接按照内存中的补码输出。

接着分析&p[4][2]和&a[4][2]什么意思

        首先我们知道p是指向a数组中第一行前四个元素的指针,是一个int[4]*的指针。

        a我们说是int[5][5]类型,而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; },那么此时a+1的步长就会是一个int[5]的大小,而我们又知道ptr1指向的就是aa数组最后一个元素后面的一个int大小的空间,代表a这个指针向后挪动了 i 步并访问这个空间的元素。那么int main() { char *a[] = {"work","at","alibaba"}; char**pa = a; pa++; printf("%s\n", *pa); return 0; }。此时的a[4]是一个int[5]类型的数组,把a[4]看做这个int[5]的数组名,那么a[4]数组名在作为指针使用的时候就是int*指针,那么a[4][2]就代表访问了a[4]数组中下标为2的元素。

        同样的道理,p是一个int[4]*的指针,我们又知道p的地址是a数组首元素的地址,指向a数组第一行的前四个元素,那么它+1的步长是int[4],"work" 字符串  是一堆char类型数据的集合,也就是char[5]数组,此时的p[4]就是一个int[4]类型的数组名,而p[4]数组名在作为指针使用的时候就是int*指针,p[4][2]就代表访问了p[4]数组中下标为2的元素。

“work”字符串就相当于char[5]的数组名,

数组名在运用的时候会变为指针,也就是会从char[5]数组变为char*指针(注:sizeof和&中的数组名除外)

我画个图,配合来看。

此时p[4][2]和a[4][2]的地址相4个int,也就是16个byte。

要知道 &p[4][2] - &a[4][2]是指针之间的元素个数,又因为p的地址比a的地址低,那么%d的形式打印出来就是-4,而%p的话则需要把-4的补码写出来,按照16进制数字进行输出。

那么为什么%p就不用像%d一样进行原码转换输出,而是直接使用补码呢?

数组名“work”作为指针的时候是指向首元素的,也就是指向w这个字符

这里我们演示一下

要达到,一眼能看出来代码在说什么的水平,这题就算OK了。

放一下结果

练习11:二维数组
当我们在char*[ ]数组中存放“work”字符串的时候,是存放了字符w的地址。

这题比较简单。

首先&aa是取出来了整个二维数组大小的地址,向后+1的步长是一个二维数组的大小,转换为int*指针便只能访问一个int,那么如图。

*(ptr-1)就是指ptr指针向前挪了一个int大小,正好指向数组最后一个元素,然后访问住户,那么结果就是10。

同样的道理,

aa是int[2][5]数组,在作为指针运用的时候是一个int[5]*指针,指向第一行的一维int[5]数组,+1的步长就是指向第二行的一维数组int[5],加上*解引用代表访问这个数组,那么也就相当于aa[1]。

实际上也可直接利用*(a+i)=a[ i ],上面只是详细解释了一下。

aa[1]的类型是int[5],此时被强制转化为int*指针,那便只能指向一个int大小的空间,也就是指向a[1][0],那么ptr2就是指向了a[1][0]。

*(ptr2-1)就是向前挪了一个int大小,指向a[0][4]元素,*代表访问元素,结果就是5。

如图。 

练习12:char*a[ ]
 char**pa = a;

        首先我们要理解char*a[ ]初始化意味着什么,要知道数组中存放的是char*类型的数据,而我们初始化的时候放的是字符串,那么字符串又意味着什么?

        可以看到  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; }。那么 char *c[] = {"ENTER","NEW","POINT","FIRST"}; char**cp[] = {c+3,c+2,c+1,c};而我们又知道,char**[ ]则代表我们存放的数据是char**这个二级指针数据。所以实际上我们存放的就是char*类型的数据,还需要知道的是数组名c存放了首元素char*的地址。

        也就是说,实际上c,c+1,c+2,c+3就是char**类型的数据

c,c+1,c+2,c+3便分别指向c数组中下标为0,1,2,3,4,的元素

而这些元素的类型都是char*类型,

上面这个语句的意思是,现在我们用pa指针指向a数组中首元素的地址,指向的数据类型是char*。

也就是说指向第一个char*。如图。

         pa++是指向第二个char*的指针,现在对指针进行*解引用,也就是访问这个char*元素,而我们在打印的时候需要传递的就是char*数据,这个char*指向字符串“at”当中'a'的地址,所以打印的结果是at。

练习13:char***之终极练习
c,c+1,c+2,c+3这四个char**元素,分别存放了下标为0,1,2,3,4,的char*元素的地址。(指向谁,存放谁的地址)

 首先梳理第一句的意思,我们在上面已经分析过char*[ ]当中存放字符串意味着什么,这里便只放图了。

 存放了四个字符串首元素的地址。

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);,那么我们又知道c是char*[4]数组,是一个数组名,而数组名在运用的时候就会变为指向首元素的指针,也就是从char*[4]类型变为char**指针,代表指向char*数据的指针,也就是 *++cpp=c+2。所以 printf("%s\n", **++cpp); printf("%s\n", *--*++cpp+3); printf("%s\n", *cpp[-2]+3); printf("%s\n", cpp[-1][-1]+1);,又因为c是指向数组首元素的,那么c+1,c+2,c+3便是指针c向后移动的步数,c是一个char**指针,移动的步长是char*,那么此时的 第二句的cpp是上一句的++cpp,是指向c+2的。总体过程是:*--*++cpp + 3 = *--(c+1)+ 3 = *c+3 = "ENTER" + 3 = "ER"也就是 "ENTER"(字符串数组名,实际上是首元素'E'的地址,类型char*)

 

接着是第三句。

 printf("%s\n", **++cpp);
 printf("%s\n", *--*++cpp+3);
 printf("%s\n", *cpp[-2]+3);
 printf("%s\n", cpp[-1][-1]+1);
 char***是指向char**数据的指针,此时的cp是指向首元素c+3这个char**的char***指针,把cp的地址交给cpp,也就代表cpp和cp一样,是指向首元素c+3这个char**的char***指针,也就是存放了c+3这个char**的地址。
*cpp[-2]+3 = *(*(cpp-2))+3 = *(c+3)+3 = "FIRST" + 3 = "ST"

先分析第一句,++cpp代表cpp向后挪动了一步,这一步的大小是一个char**。++cpp的类型是char***,对++cpp进行解引用,*++cpp就是char**类型,也就是在访问++cpp所指向的对象,此时

++cpp指向c+2,那么"FIRST"(字符串数组名,实际上是首元素'F'的地址,类型char*)

再进行解引用的话,就 相当于对c+2进行解引用,c+2指向的对象是  "POINT" 的 首字符 'P' 地址,对c+2进行解引用代表访问  c+2所指向的对象,也就是访问"POINT" 的 首字符 'P' 地址,此时把这个地址用%s的形式打印, 那么便是POINT。 如图。

 现在分析第二句。

 printf("%s\n", **++cpp);
 printf("%s\n", *--*++cpp+3);
 printf("%s\n", *cpp[-2]+3);
 printf("%s\n", cpp[-1][-1]+1);

 我们需要知道的是前置后置++和--都会对数据本身造成影响,也就是说cpp[-1][-1]+1 = *(*(cpp-1)-1)+ 1 = *((c+2)-1)+1 = *(c+1)+1 = "NEW"+1=其次,我们要先运算*--*++cpp+3中的++cpp。那么++cpp指向c+1,解引用的话就是访问++cpp指向的对象,++cpp指向c+1,那么*++cpp=c+1,然后我们再对c+1进行前置--,那么c+1就会变为c。再对c进行解引用,就是在访问c所指向的对象,c指向ENTER字符串中E的地址,再对这个地址+3就意味着,从字符E向后走了3步,每步大小是一个字符char,那么打印结果是ER。

"EW" "NEW"(字符串数组名,实际上是首元素'N'的地址,类型char*)

 分析第三句

直接写出整体运算过程
此时的cpp指向cp数组中第三个元素,(不再细说,道理想通) 分析最后一句
 
 

因为上一句并不是使用的前置后置++或者--,所以cpp还是指向cp数组中第三个元素。

[+++]

[+++]

 这题达到能[+++]的水平就可以了。

练习到此,后续还会有练习,不过这算是比较深入的练习了,如果我没解释清楚,可以看我之前对于指针文章的解释,或者直接私信我,很乐意解答,可惜没人看哈哈哈

)
File: /www/wwwroot/outofmemory.cn/tmp/route_read.php, Line: 126, InsideLink()
File: /www/wwwroot/outofmemory.cn/tmp/index.inc.php, Line: 166, include(/www/wwwroot/outofmemory.cn/tmp/route_read.php)
File: /www/wwwroot/outofmemory.cn/index.php, Line: 30, include(/www/wwwroot/outofmemory.cn/tmp/index.inc.php)
Error[8]: Undefined offset: 125, File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 121
File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 473, decode(

上一篇:从0开始学c语言-29-数据储存练习、筑基期练习、两步翻转_阿秋的阿秋不是阿秋的博客-CSDN博客

应有的基础:

从0开始学c语言-28-qsort函数、 数组和指针参数、函数指针数组(转移表)、回调函数_阿秋的阿秋不是阿秋的博客-CSDN博客

从0开始学c语言-27-字符指针,指针数组和数组指针_阿秋的阿秋不是阿秋的博客-CSDN博客

目录

练习1:int arr[ ]

 练习2:char arr[ ] zifu

 对比

 练习3:char arr[ ]

 对比\0

 练习4:char*p

对比 

 练习5:二维数组

 练习6:判断程序结果

练习7:第一眼没看出来的考点(重点看)

***指针和地址的新理解***

分析题

练习8:%x?int+1?(又被击败)

对于非指针+1的步长理解(地址神奇~)

练习9:有点陷阱

练习10:二维数组指针运用、指针运算、%p打印整型

练习11:二维数组

练习12:char*a[ ]

练习13:char***之终极练习


练习1:int arr[ ]

应有的知识:

数组名的意义: 1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。 2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。 3. 除此之外所有的数组名都表示首元素的地址。
//一维数组
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a+0));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(a[1]));
printf("%d\n",sizeof(&a));
printf("%d\n",sizeof(*&a));
printf("%d\n",sizeof(&a+1));
printf("%d\n",sizeof(&a[0]));
printf("%d\n",sizeof(&a[0]+1));

请思考每个printf函数输出的结果是多少。

开始讲

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));

a单纯的数组名实际上就是数组首元素的地址,那么地址的大小在32位系统上是4byte,在64位系统上是8byte大小。但是呢,sizeof当中的数组名则代表整个数组,这一点要特别注意,那么a数组的大小就是4个int的大小,也就是16byte。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a+0));

实际上地址就是指针,那么a+0就代表a这个指针向后挪动了0步。且a本身指向数组首元素,+0表示一步没有挪动。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(*a));

我们知道a是指针,*a就是对指针进行解引用,a指向数组首元素,那么*a就是在访问数组首元素,而数组首元素的类型是int,int的大小是4byte。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a+1));

a+0和a+1都是指针,只不过一个指向首元素,一个指向下标为1的元素。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a[1]));

a[1]代表数组下标为1的元素,而这个元素的类型是int,那么就是4byte。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(&a));

我们早在前面说过,

1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。 2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
那么现在是sizeof和&数组名组合到一起,这时候,sizeof当中的&数组名,则是计算的 【指向整个数组的指针】的大小。
printf("%d\n",sizeof(*&a));

现在在&a前面加上了*, 而*&a=a,a又在sizeof当中,那就代表我们访问了整个数组,应该是16byte。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(&a+1));

&a代表指向整个数组的指针,那么+1则会让指针向后挪动一步,这一步的大小是整个数组的大小,我们计算的还是 指针的大小。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(&a[0]));

&a[0]是什么意思?

如果讲结合性的话,我还不太熟练,只能放图监视给你看

 也就是说&a[0]=&(a[0])

意思是取出数组第一个元素的地址,本质上是个指针。
printf("%d\n",sizeof(&a[0]+1));

那么同样的,&a[0]+1就代表指向第一个元素的指针向后走了一步,而这一步的大小是一个int,也就是指向了下标为1的元素。

现在看看结果,是否符合我们的推理。

 练习2:char arr[ ] zifu
char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));
	printf("%d\n", sizeof(arr + 0));
	printf("%d\n", sizeof(*arr));
	printf("%d\n", sizeof(arr[1]));
	printf("%d\n", sizeof(&arr));
	printf("%d\n", sizeof(&arr + 1));
	printf("%d\n", sizeof(&arr[0] + 1));

实际上,这题和上面那道题几乎一模一样。我就把解释写在代码里了哈。

char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));
//整个数组的大小,6
	printf("%d\n", sizeof(arr + 0));
//指向下标1的指针,4/8 ,8代表64位系统的结果
	printf("%d\n", sizeof(*arr));
//访问数组第一个元素,1
	printf("%d\n", sizeof(arr[1]));
//访问数组第一个元素,1
	printf("%d\n", sizeof(&arr));
//指向整个数组的指针,4/8
	printf("%d\n", sizeof(&arr + 1));
//指向整个数组的指针向后挪动一步,4/8
	printf("%d\n", sizeof(&arr[0] + 1));
//指向数组第一个元素的指针向后挪动一步,4/8

 对比
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));

实际上大体也是一样的,只不过我们换成了strlen,那就要特别注意\0。

char arr[] = { 'a','b','c','d','e','f' };
    printf("%d\n", strlen(arr));
//没有char arr[] = "abcdef";
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr+0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr+1));
printf("%d\n", sizeof(&arr[0]+1));,随机值
    printf("%d\n", strlen(arr+0));
//没有char arr[] = "abcdef";
    printf("%d\n", sizeof(arr));
//sizeof中的数组名代表整个数组,7(记得有隐藏的char arr[] = "abcdef";
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));元素)
    printf("%d\n", sizeof(arr+0));
//arr+0代表指针,指向第一个元素,4/8 (8是64位系统的结果)
    printf("%d\n", sizeof(*arr));
//代表字符串的第一个字符,类型char,1
    printf("%d\n", sizeof(arr[1]));
//代表字符串的第一个字符,类型char,1
    printf("%d\n", sizeof(&arr));
//指向整个char数组的指针,4/8
    printf("%d\n", sizeof(&arr+1));
//指向整个char数组的指针向后挪一步,4/8
    printf("%d\n", sizeof(&arr[0]+1));
//指向字符串第一个字符的指针向后挪一步,4/8,随机值
    printf("%d\n", strlen(*arr));
//*arr不是指针,不能计算
    printf("%d\n", strlen(arr[1]));
//arr[1]不是指针,不能计算
    printf("%d\n", strlen(&arr));
//没有char arr[] = "abcdef";
    printf("%d\n", strlen(arr));
//6
    printf("%d\n", strlen(arr+0));
//6
    printf("%d\n", strlen(*arr));
//不能计算,strlen要的是const char*指针类型的数据
    printf("%d\n", strlen(arr[1]));
//不能计算,strlen要的是const char*指针类型的数据
//虽然后面这两个语句不是const char*类型,但是可以把地址传过去
    printf("%d\n", strlen(&arr));
//6
    printf("%d\n", strlen(&arr+1));
//指向数组的指针向后挪动一步
//这一步的大小是一个数组的大小,打印结果随机值
    printf("%d\n", strlen(&arr[0]+1));
//指向数组中首元素的指针向后挪一步
//这步大小是一个char的大小,打印结果是5,随机值
    printf("%d\n", strlen(&arr+1));
//没有char *p = "abcdef";
printf("%d\n", sizeof(p));
printf("%d\n", sizeof(p+1));
printf("%d\n", sizeof(*p));
printf("%d\n", sizeof(p[0]));
printf("%d\n", sizeof(&p));
printf("%d\n", sizeof(&p+1));
printf("%d\n", sizeof(&p[0]+1));,随机值-6  因为是指向整个数组的指针向后挪一步
    printf("%d\n", strlen(&arr[0]+1));
//没有char *p = "abcdef";
    printf("%d\n", sizeof(p));
//p是个指针,4/8 (8是64位系统的结果)
    printf("%d\n", sizeof(p+1));
//p+1指向下一个字符c,4/8
    printf("%d\n", sizeof(*p));
//*p代表首字符字符a,1
    printf("%d\n", sizeof(p[0]));
//代表首字符字符a,1
    printf("%d\n", sizeof(&p));
//&p代表指针p的地址,4/8
    printf("%d\n", sizeof(&p+1));
//指向指针p的指针向后挪动一步,这一步大小是一个char*,4/8
    printf("%d\n", sizeof(&p[0]+1));
//指向首字符的指针向后挪动一步,这一步的大小是一个char,4/8,随机值-1 因为是指向一个元素的指针向后挪一步

很多警告,主要看类型不同的警告,正如我们所说,*arr和arr[0]是不能计算的,

在注释掉后,计算的结果就像我们注释里说的。

 练习3:char arr[ ]
char *p = "abcdef";
printf("%d\n", strlen(p));
printf("%d\n", strlen(p+1));
printf("%d\n", strlen(*p));
printf("%d\n", strlen(p[0]));
printf("%d\n", strlen(&p));
printf("%d\n", strlen(&p+1));
printf("%d\n", strlen(&p[0]+1));

要知道的是,储存字符串当中的字符数组名是第一个字符的地址。

char *p = "abcdef";
    printf("%d\n", strlen(p));
//6
    printf("%d\n", strlen(p+1));
//5
    printf("%d\n", strlen(*p));
    printf("%d\n", strlen(p[0]));
//上面的*p和p[0]是char类型,不能计算
    printf("%d\n", strlen(&p));
//p此时是个指向首元素的指针,而&p代表指向p的指针
//那么&p中保存的是p的地址,int a[3][4] = {0};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a[0][0]));
printf("%d\n",sizeof(a[0]));
printf("%d\n",sizeof(a[0]+1));
printf("%d\n",sizeof(*(a[0]+1)));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(*(a+1)));
printf("%d\n",sizeof(&a[0]+1));
printf("%d\n",sizeof(*(&a[0]+1)));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a[3]));位置不确定,计算值会是个随机值
    printf("%d\n", strlen(&p+1));
//&p是个指向p的指针,类型char**,向后挪动一步的大小是char*
//char*是个指针,指针的大小是4/8
//所以结果是随机值-4/8 (8是64位系统的)
    printf("%d\n", strlen(&p[0]+1));
//5

 对比\0
int a[3][4] = {0};
    printf("%d\n",sizeof(a));
//sizeof中的a代表整个数组,12*4=48
    printf("%d\n",sizeof(a[0][0]));
//第一行第一个元素,4

注意strlen是找\0来结束运算的,而字符串后会有一个隐藏的\0

printf("%d\n",sizeof(a[0]));
//实际上a[0]是第一行一维数组的数组名,那么
//sizeof中的数组名代表整个数组
//而数组名a[0]其实就是个指针,指向4个int
//二维数组中第一行的大小,第一行4个元素
//结果是4*4=16

 练习4:char*p
printf("%d\n",sizeof(a[0]+1));
//a[0]的类型是int[4],作为指针就是int*,指向int
//向后加一的步长是int,指向第一行下标为1的元素
//输出结果是4/8

第一句的意思是,char*指针p指向后面字符串首元素a的地址。

printf("%d\n",sizeof(*(a[0]+1)));
//a[0]+1代表指向 第一行下标为1的元素 的指针
//解引用后代表访问这个元素
//元素类型int,那么结果为4

对比 
printf("%d\n",sizeof(a+1));
//a的类型是int[3][4]
//作为指针的话是一个一维数组int[4]*
//+1的步长是4个int,那么a+1就代表第二行的地址
//结果是4/8
    printf("%d\n",sizeof(*(a+1)));
//*(a+1)=a[1],是第二行的一维数组名
//sizeof中的数组名代表整个数组的大小
//此时的a[1]是第二行,有4个元素
//那就是4个int,16byte

依旧注意p是个指向a的指针,strlen主要看\0的位置。

printf("%d\n",sizeof(&a[0]+1));
//a[0]代表第一行的数组名
//&a[0]就代表指向第一行的指针
//此时的+1步长是 一行数组 的大小
//指针指向第二行的一维数组
//结果4/8
    printf("%d\n",sizeof(*(&a[0]+1)));
//&a[0]+1代表 指向第二行一维数组的指针
//对指针解引用就是a[1]
//结果4*4=16

 练习5:二维数组
printf("%d\n",sizeof(*a));
//a的类型是int[3][4],作为指针是int[4]*
//解引用后是int[4]
//那么*a就代表第一行的数组名
//实际上*a=*(a+0)=a[0]
//那么结果是4*4=16
    printf("%d\n",sizeof(a[3]));
//二维数组一共三行,最大是a[2]
//sizeof中的a[3]结果是这个表达式的类型大小
//所以不涉及越界访问的问题
//直接就是一行元素的大小,16

二维数组的话要明白,二维数组的数组名在作为指针的情况下是个一维数组指针。

int main()
{
    int a[5] = { 1, 2, 3, 4, 5 };
    int *ptr = (int *)(&a + 1);
    printf( "%d,%d", *(a + 1), *(ptr - 1));
    return 0; }
//由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
 int Num;
 char *pcName;
 short sDate;
 char cha[2];
 short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
 printf("%p\n", p + 0x1);
 printf("%p\n", (unsigned long)p + 0x1);
 printf("%p\n", (unsigned int*)p + 0x1);
 return 0; }
还不完全一样
我们得看指针类型,才能确定相邻的指针会差多少字节。
非要说有的话,也只有在按照不同数据进行分配空间后才算是有了类型。
而地址在有了类型后,也就是指针了。
以字节的方式来理解地址和指针

 练习6:判断程序结果
//假设p 的值为0x100000。 如下表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
     printf("%p\n", p + 0x1);
//p+1的步长是20字节 - 十六进制是14
//0x100014
     printf("%p\n", (unsigned long)p + 0x1);
//注意是把p这个结构体指针转换成一个整型了
//所以是加了十六进制的数字1
//结果是0x100001
     printf("%p\n", (unsigned int*)p + 0x1);
//p+1的步长是4或者8字节(32位/64位系统)
//0x100004

首先看*(a+1),a+1代表下标为1的地址,*(a+1)=a[1],结果是2

*(ptr - 1),先分析ptr接收了谁的地址

&a+1 代表 &a是整个数组的地址,+1则是指向a后面一个数组大小的地址

int*强制类型转换后,只能访问一个int大小的地址并存到ptr中,

此时ptr指向a[4]这个元素后面的地址,ptr是int*指针

那么ptr-1的步长是一个int,ptr-1指向a[4],解引用后就是5

output:2,5

练习7:第一眼没看出来的考点(重点看)
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; }

说实话,这个题我做过一遍,第二遍还是没看出来在干嘛

然后我思考了一下原因,就在于我被0x1迷惑了。我一看到0x1完全忘了指针p的作用,只想着地址加1是怎么加啊?

我一直在想地址加1难道不是纯粹的加1吗?这题为啥要这么写?

还真不是!!!

***指针和地址的新理解***

因为你要知道的是其实指针就是地址,地址就是指针

但是还有一个问题,那为什么普通的整型+1就是数值上的+1而不是字节上的+1呢?,因为如果单纯的说地址,那地址是一个储存单元一个地址,相邻的地址会相差1个字节,但是相邻的指针会相差一个字节吗?

那还真不是,地址之间相邻差1byte,所以+1 的步长就是1byte

那么话说回来了,地址会有类型吗? 那还真没有,如果 (int)a (int)a+1
但是并没有看到说对地址分类的说法,所以我建议是 ,也就是说,无论是地址还是字节,在分析+1步长的时候,得 看看它指向的类型占了几个字节来判断。
我觉得这个理解超级对!!以后的我,常回来看看,有新的理解一定要及时改动! 分析题 现在来分析题目,其实题目考察的就是指针类型对于+1步长的影响。
 
 练习8:%x?int+1?(又被击败) 
 
 
 

先分析ptr1指针接收到的地址是谁

&a是整个数组的大小,+1后指向a数组后面 一个数组大小 的空间

int*强制类型转换后就只能访问一个int大小的空间,把这个空间(也就是a[3]后面的int空间)存到了ptr1中

对于非指针+1的步长理解(地址神奇~)

接着是ptr2,首先a是指向数组首元素的,但是被强制转换为了int类型的数据,那么(int)a+1的步长会是多少呢?

int*+1会跳过4个字节,因为指针指向的int占4byte

char*+1跳过1个字节,因为指针指向的char占1byte

类似的,

我们的int+1是多大呢?难道是单纯的1+1=2吗?

我的思考过程是这样

        首先a是数组名,指向首元素的地址,在int强制转换后,是把一个地址强制转换为了int类型。按理说,int+1应该是按照一个int大小来向后挪动的啊!但是这样的话,int*指针又该怎么解释步长是一个int大小呢?

        关于这个问题,其实上个练习也涉及到了。需要知道的是我们在转换为int的时候,是把地址转换为了int类型的数据,也就是说此时的地址不是指向某个数据的指针,而是单纯的数字,其实就是给地址编号加了个数字1,而。

那这时候我们知道了(int)a+1就是a内存中第二个字节的地址,此时把这个地址转换为int*指针,而一个int*指针可以访问4个字节。我们按照小端储存来看看此时的ptr储存的是哪些。

00000002那么 p 指向的就是 a 数组中第一行的前四个元素。a作为指针使用的时候就是int[5]*指针*(a+i)=a[ i ]
010000
00(我加了#,因为没有0x看着别扭)int main() {    int a[3][2] = { (0, 1), (2, 3), (4, 5) };    int *p;    p = a[0];    printf( "%d", p[0]); return 0; }int a[3][2] = { (0, 1), (2, 3), (4, 5) }; int a[3][2] = { 1, 3, 5 };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; }

上面标红的就是ptr指向的内存单元。

那么打印的结果

ptr[-1]是指向ptr指针前面一个int空间的数据,这个空间的数据是a[3]。

按照小端储存的内存中是(低地址)04 00 00 00(高地址)

因为我们把数据存放到内存中按照小端储存,那就是数据的低位放到低地址,高位放到高地址,所以打印出来就是(高位)00 00 00 04 (低位)(省略前面的0)

*ptr则是解引用ptr指针,去通过这个地址访问,访问到的是(低地址)00 00 00 02(高地址)

因为我们把数据存放到内存中按照小端储存,那就是数据的低位放到低地址,高位放到高地址,打印出来的就是(高位)02 00 00 00(低位)(省略前面的0)

看结果a[4]的意思就是a向后挪动了4个步长,每个步长是int[5],那么此时的a[4]已经跳过了4*5=20个int元素

 地址转换为int+1,就是单纯的数字+1啦,实际上就是跳过了一个byte的地址。

练习9:有点陷阱
p[4]就代表p指针向后挪动了4个步长,每个步长是int[4],一共跳过了4*4=16个int元素

这题其实挺搞笑的,要是看走眼就无了

你要注意的是后面是逗号表达式,所以第一行的语句实际上就是

总结:二维数组

a[0]代表二维数组第一行,也就是1和3两个元素。

我们的int*p指向指向了a[0]这个一维数组,也就是说我们指向了第一行int[2]大小的首元素地址,因为p是int*指针,便一次指向一个int大小的空间,也就是指向首元素1。

而打印中的p[0]实际上就等价于*(p+0),也就是首元素1。

练习10:二维数组指针运用、指针运算、%p打印整型
p [4][2]=* ( * ( p + 4 ) + 2 );   

a的类型是int[5][5]二维数组,我们的p是int[4]*类型的指针,指向int[4]。

此时我们把p=a,而 a 没有被&也没有在sizeof当中,所以是数组首元素的地址,也就是第一行

因为地址无所谓正负,所以%p认为内存中存的就是一个地址,直接按照内存中的补码输出。

接着分析&p[4][2]和&a[4][2]什么意思

        首先我们知道p是指向a数组中第一行前四个元素的指针,是一个int[4]*的指针。

        a我们说是int[5][5]类型,而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; },那么此时a+1的步长就会是一个int[5]的大小,而我们又知道ptr1指向的就是aa数组最后一个元素后面的一个int大小的空间,代表a这个指针向后挪动了 i 步并访问这个空间的元素。那么int main() { char *a[] = {"work","at","alibaba"}; char**pa = a; pa++; printf("%s\n", *pa); return 0; }。此时的a[4]是一个int[5]类型的数组,把a[4]看做这个int[5]的数组名,那么a[4]数组名在作为指针使用的时候就是int*指针,那么a[4][2]就代表访问了a[4]数组中下标为2的元素。

        同样的道理,p是一个int[4]*的指针,我们又知道p的地址是a数组首元素的地址,指向a数组第一行的前四个元素,那么它+1的步长是int[4],"work" 字符串  是一堆char类型数据的集合,也就是char[5]数组,此时的p[4]就是一个int[4]类型的数组名,而p[4]数组名在作为指针使用的时候就是int*指针,p[4][2]就代表访问了p[4]数组中下标为2的元素。

“work”字符串就相当于char[5]的数组名,

数组名在运用的时候会变为指针,也就是会从char[5]数组变为char*指针(注:sizeof和&中的数组名除外)

我画个图,配合来看。

此时p[4][2]和a[4][2]的地址相4个int,也就是16个byte。

要知道 &p[4][2] - &a[4][2]是指针之间的元素个数,又因为p的地址比a的地址低,那么%d的形式打印出来就是-4,而%p的话则需要把-4的补码写出来,按照16进制数字进行输出。

那么为什么%p就不用像%d一样进行原码转换输出,而是直接使用补码呢?

数组名“work”作为指针的时候是指向首元素的,也就是指向w这个字符

这里我们演示一下

要达到,一眼能看出来代码在说什么的水平,这题就算OK了。

放一下结果

练习11:二维数组
当我们在char*[ ]数组中存放“work”字符串的时候,是存放了字符w的地址。

这题比较简单。

首先&aa是取出来了整个二维数组大小的地址,向后+1的步长是一个二维数组的大小,转换为int*指针便只能访问一个int,那么如图。

*(ptr-1)就是指ptr指针向前挪了一个int大小,正好指向数组最后一个元素,然后访问住户,那么结果就是10。

同样的道理,

aa是int[2][5]数组,在作为指针运用的时候是一个int[5]*指针,指向第一行的一维int[5]数组,+1的步长就是指向第二行的一维数组int[5],加上*解引用代表访问这个数组,那么也就相当于aa[1]。

实际上也可直接利用*(a+i)=a[ i ],上面只是详细解释了一下。

aa[1]的类型是int[5],此时被强制转化为int*指针,那便只能指向一个int大小的空间,也就是指向a[1][0],那么ptr2就是指向了a[1][0]。

*(ptr2-1)就是向前挪了一个int大小,指向a[0][4]元素,*代表访问元素,结果就是5。

如图。 

练习12:char*a[ ]
 char**pa = a;

        首先我们要理解char*a[ ]初始化意味着什么,要知道数组中存放的是char*类型的数据,而我们初始化的时候放的是字符串,那么字符串又意味着什么?

        可以看到  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; }。那么 char *c[] = {"ENTER","NEW","POINT","FIRST"}; char**cp[] = {c+3,c+2,c+1,c};而我们又知道,char**[ ]则代表我们存放的数据是char**这个二级指针数据。所以实际上我们存放的就是char*类型的数据,还需要知道的是数组名c存放了首元素char*的地址。

        也就是说,实际上c,c+1,c+2,c+3就是char**类型的数据

c,c+1,c+2,c+3便分别指向c数组中下标为0,1,2,3,4,的元素

而这些元素的类型都是char*类型,

上面这个语句的意思是,现在我们用pa指针指向a数组中首元素的地址,指向的数据类型是char*。

也就是说指向第一个char*。如图。

         pa++是指向第二个char*的指针,现在对指针进行*解引用,也就是访问这个char*元素,而我们在打印的时候需要传递的就是char*数据,这个char*指向字符串“at”当中'a'的地址,所以打印的结果是at。

练习13:char***之终极练习
c,c+1,c+2,c+3这四个char**元素,分别存放了下标为0,1,2,3,4,的char*元素的地址。(指向谁,存放谁的地址)

 首先梳理第一句的意思,我们在上面已经分析过char*[ ]当中存放字符串意味着什么,这里便只放图了。

 存放了四个字符串首元素的地址。

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);,那么我们又知道c是char*[4]数组,是一个数组名,而数组名在运用的时候就会变为指向首元素的指针,也就是从char*[4]类型变为char**指针,代表指向char*数据的指针,也就是 *++cpp=c+2。所以 printf("%s\n", **++cpp); printf("%s\n", *--*++cpp+3); printf("%s\n", *cpp[-2]+3); printf("%s\n", cpp[-1][-1]+1);,又因为c是指向数组首元素的,那么c+1,c+2,c+3便是指针c向后移动的步数,c是一个char**指针,移动的步长是char*,那么此时的 第二句的cpp是上一句的++cpp,是指向c+2的。总体过程是:*--*++cpp + 3 = *--(c+1)+ 3 = *c+3 = "ENTER" + 3 = "ER"也就是 "ENTER"(字符串数组名,实际上是首元素'E'的地址,类型char*)

 

接着是第三句。

 printf("%s\n", **++cpp);
 printf("%s\n", *--*++cpp+3);
 printf("%s\n", *cpp[-2]+3);
 printf("%s\n", cpp[-1][-1]+1);
 char***是指向char**数据的指针,此时的cp是指向首元素c+3这个char**的char***指针,把cp的地址交给cpp,也就代表cpp和cp一样,是指向首元素c+3这个char**的char***指针,也就是存放了c+3这个char**的地址。
*cpp[-2]+3 = *(*(cpp-2))+3 = *(c+3)+3 = "FIRST" + 3 = "ST"

先分析第一句,++cpp代表cpp向后挪动了一步,这一步的大小是一个char**。++cpp的类型是char***,对++cpp进行解引用,*++cpp就是char**类型,也就是在访问++cpp所指向的对象,此时

++cpp指向c+2,那么"FIRST"(字符串数组名,实际上是首元素'F'的地址,类型char*)

再进行解引用的话,就 相当于对c+2进行解引用,c+2指向的对象是  "POINT" 的 首字符 'P' 地址,对c+2进行解引用代表访问  c+2所指向的对象,也就是访问"POINT" 的 首字符 'P' 地址,此时把这个地址用%s的形式打印, 那么便是POINT。 如图。

 现在分析第二句。

 printf("%s\n", **++cpp);
 printf("%s\n", *--*++cpp+3);
 printf("%s\n", *cpp[-2]+3);
 printf("%s\n", cpp[-1][-1]+1);

 我们需要知道的是前置后置++和--都会对数据本身造成影响,也就是说cpp[-1][-1]+1 = *(*(cpp-1)-1)+ 1 = *((c+2)-1)+1 = *(c+1)+1 = "NEW"+1=其次,我们要先运算*--*++cpp+3中的++cpp。那么++cpp指向c+1,解引用的话就是访问++cpp指向的对象,++cpp指向c+1,那么*++cpp=c+1,然后我们再对c+1进行前置--,那么c+1就会变为c。再对c进行解引用,就是在访问c所指向的对象,c指向ENTER字符串中E的地址,再对这个地址+3就意味着,从字符E向后走了3步,每步大小是一个字符char,那么打印结果是ER。

"EW" "NEW"(字符串数组名,实际上是首元素'N'的地址,类型char*)

 分析第三句

直接写出整体运算过程
此时的cpp指向cp数组中第三个元素,(不再细说,道理想通) 分析最后一句
 
 

因为上一句并不是使用的前置后置++或者--,所以cpp还是指向cp数组中第三个元素。

[+++]

 这题达到能[+++]的水平就可以了。

练习到此,后续还会有练习,不过这算是比较深入的练习了,如果我没解释清楚,可以看我之前对于指针文章的解释,或者直接私信我,很乐意解答,可惜没人看哈哈哈

)
File: /www/wwwroot/outofmemory.cn/tmp/route_read.php, Line: 126, InsideLink()
File: /www/wwwroot/outofmemory.cn/tmp/index.inc.php, Line: 166, include(/www/wwwroot/outofmemory.cn/tmp/route_read.php)
File: /www/wwwroot/outofmemory.cn/index.php, Line: 30, include(/www/wwwroot/outofmemory.cn/tmp/index.inc.php)
Error[8]: Undefined offset: 126, File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 121
File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 473, decode(

上一篇:从0开始学c语言-29-数据储存练习、筑基期练习、两步翻转_阿秋的阿秋不是阿秋的博客-CSDN博客

应有的基础:

从0开始学c语言-28-qsort函数、 数组和指针参数、函数指针数组(转移表)、回调函数_阿秋的阿秋不是阿秋的博客-CSDN博客

从0开始学c语言-27-字符指针,指针数组和数组指针_阿秋的阿秋不是阿秋的博客-CSDN博客

目录

练习1:int arr[ ]

 练习2:char arr[ ] zifu

 对比

 练习3:char arr[ ]

 对比\0

 练习4:char*p

对比 

 练习5:二维数组

 练习6:判断程序结果

练习7:第一眼没看出来的考点(重点看)

***指针和地址的新理解***

分析题

练习8:%x?int+1?(又被击败)

对于非指针+1的步长理解(地址神奇~)

练习9:有点陷阱

练习10:二维数组指针运用、指针运算、%p打印整型

练习11:二维数组

练习12:char*a[ ]

练习13:char***之终极练习


练习1:int arr[ ]

应有的知识:

数组名的意义: 1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。 2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。 3. 除此之外所有的数组名都表示首元素的地址。
//一维数组
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a+0));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(a[1]));
printf("%d\n",sizeof(&a));
printf("%d\n",sizeof(*&a));
printf("%d\n",sizeof(&a+1));
printf("%d\n",sizeof(&a[0]));
printf("%d\n",sizeof(&a[0]+1));

请思考每个printf函数输出的结果是多少。

开始讲

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));

a单纯的数组名实际上就是数组首元素的地址,那么地址的大小在32位系统上是4byte,在64位系统上是8byte大小。但是呢,sizeof当中的数组名则代表整个数组,这一点要特别注意,那么a数组的大小就是4个int的大小,也就是16byte。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a+0));

实际上地址就是指针,那么a+0就代表a这个指针向后挪动了0步。且a本身指向数组首元素,+0表示一步没有挪动。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(*a));

我们知道a是指针,*a就是对指针进行解引用,a指向数组首元素,那么*a就是在访问数组首元素,而数组首元素的类型是int,int的大小是4byte。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a+1));

a+0和a+1都是指针,只不过一个指向首元素,一个指向下标为1的元素。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a[1]));

a[1]代表数组下标为1的元素,而这个元素的类型是int,那么就是4byte。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(&a));

我们早在前面说过,

1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。 2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
那么现在是sizeof和&数组名组合到一起,这时候,sizeof当中的&数组名,则是计算的 【指向整个数组的指针】的大小。
printf("%d\n",sizeof(*&a));

现在在&a前面加上了*, 而*&a=a,a又在sizeof当中,那就代表我们访问了整个数组,应该是16byte。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(&a+1));

&a代表指向整个数组的指针,那么+1则会让指针向后挪动一步,这一步的大小是整个数组的大小,我们计算的还是 指针的大小。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(&a[0]));

&a[0]是什么意思?

如果讲结合性的话,我还不太熟练,只能放图监视给你看

 也就是说&a[0]=&(a[0])

意思是取出数组第一个元素的地址,本质上是个指针。
printf("%d\n",sizeof(&a[0]+1));

那么同样的,&a[0]+1就代表指向第一个元素的指针向后走了一步,而这一步的大小是一个int,也就是指向了下标为1的元素。

现在看看结果,是否符合我们的推理。

 练习2:char arr[ ] zifu
char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));
	printf("%d\n", sizeof(arr + 0));
	printf("%d\n", sizeof(*arr));
	printf("%d\n", sizeof(arr[1]));
	printf("%d\n", sizeof(&arr));
	printf("%d\n", sizeof(&arr + 1));
	printf("%d\n", sizeof(&arr[0] + 1));

实际上,这题和上面那道题几乎一模一样。我就把解释写在代码里了哈。

char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));
//整个数组的大小,6
	printf("%d\n", sizeof(arr + 0));
//指向下标1的指针,4/8 ,8代表64位系统的结果
	printf("%d\n", sizeof(*arr));
//访问数组第一个元素,1
	printf("%d\n", sizeof(arr[1]));
//访问数组第一个元素,1
	printf("%d\n", sizeof(&arr));
//指向整个数组的指针,4/8
	printf("%d\n", sizeof(&arr + 1));
//指向整个数组的指针向后挪动一步,4/8
	printf("%d\n", sizeof(&arr[0] + 1));
//指向数组第一个元素的指针向后挪动一步,4/8

 对比
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));

实际上大体也是一样的,只不过我们换成了strlen,那就要特别注意\0。

char arr[] = { 'a','b','c','d','e','f' };
    printf("%d\n", strlen(arr));
//没有char arr[] = "abcdef";
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr+0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr+1));
printf("%d\n", sizeof(&arr[0]+1));,随机值
    printf("%d\n", strlen(arr+0));
//没有char arr[] = "abcdef";
    printf("%d\n", sizeof(arr));
//sizeof中的数组名代表整个数组,7(记得有隐藏的char arr[] = "abcdef";
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));元素)
    printf("%d\n", sizeof(arr+0));
//arr+0代表指针,指向第一个元素,4/8 (8是64位系统的结果)
    printf("%d\n", sizeof(*arr));
//代表字符串的第一个字符,类型char,1
    printf("%d\n", sizeof(arr[1]));
//代表字符串的第一个字符,类型char,1
    printf("%d\n", sizeof(&arr));
//指向整个char数组的指针,4/8
    printf("%d\n", sizeof(&arr+1));
//指向整个char数组的指针向后挪一步,4/8
    printf("%d\n", sizeof(&arr[0]+1));
//指向字符串第一个字符的指针向后挪一步,4/8,随机值
    printf("%d\n", strlen(*arr));
//*arr不是指针,不能计算
    printf("%d\n", strlen(arr[1]));
//arr[1]不是指针,不能计算
    printf("%d\n", strlen(&arr));
//没有char arr[] = "abcdef";
    printf("%d\n", strlen(arr));
//6
    printf("%d\n", strlen(arr+0));
//6
    printf("%d\n", strlen(*arr));
//不能计算,strlen要的是const char*指针类型的数据
    printf("%d\n", strlen(arr[1]));
//不能计算,strlen要的是const char*指针类型的数据
//虽然后面这两个语句不是const char*类型,但是可以把地址传过去
    printf("%d\n", strlen(&arr));
//6
    printf("%d\n", strlen(&arr+1));
//指向数组的指针向后挪动一步
//这一步的大小是一个数组的大小,打印结果随机值
    printf("%d\n", strlen(&arr[0]+1));
//指向数组中首元素的指针向后挪一步
//这步大小是一个char的大小,打印结果是5,随机值
    printf("%d\n", strlen(&arr+1));
//没有char *p = "abcdef";
printf("%d\n", sizeof(p));
printf("%d\n", sizeof(p+1));
printf("%d\n", sizeof(*p));
printf("%d\n", sizeof(p[0]));
printf("%d\n", sizeof(&p));
printf("%d\n", sizeof(&p+1));
printf("%d\n", sizeof(&p[0]+1));,随机值-6  因为是指向整个数组的指针向后挪一步
    printf("%d\n", strlen(&arr[0]+1));
//没有char *p = "abcdef";
    printf("%d\n", sizeof(p));
//p是个指针,4/8 (8是64位系统的结果)
    printf("%d\n", sizeof(p+1));
//p+1指向下一个字符c,4/8
    printf("%d\n", sizeof(*p));
//*p代表首字符字符a,1
    printf("%d\n", sizeof(p[0]));
//代表首字符字符a,1
    printf("%d\n", sizeof(&p));
//&p代表指针p的地址,4/8
    printf("%d\n", sizeof(&p+1));
//指向指针p的指针向后挪动一步,这一步大小是一个char*,4/8
    printf("%d\n", sizeof(&p[0]+1));
//指向首字符的指针向后挪动一步,这一步的大小是一个char,4/8,随机值-1 因为是指向一个元素的指针向后挪一步

很多警告,主要看类型不同的警告,正如我们所说,*arr和arr[0]是不能计算的,

在注释掉后,计算的结果就像我们注释里说的。

 练习3:char arr[ ]
char *p = "abcdef";
printf("%d\n", strlen(p));
printf("%d\n", strlen(p+1));
printf("%d\n", strlen(*p));
printf("%d\n", strlen(p[0]));
printf("%d\n", strlen(&p));
printf("%d\n", strlen(&p+1));
printf("%d\n", strlen(&p[0]+1));

要知道的是,储存字符串当中的字符数组名是第一个字符的地址。

char *p = "abcdef";
    printf("%d\n", strlen(p));
//6
    printf("%d\n", strlen(p+1));
//5
    printf("%d\n", strlen(*p));
    printf("%d\n", strlen(p[0]));
//上面的*p和p[0]是char类型,不能计算
    printf("%d\n", strlen(&p));
//p此时是个指向首元素的指针,而&p代表指向p的指针
//那么&p中保存的是p的地址,int a[3][4] = {0};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a[0][0]));
printf("%d\n",sizeof(a[0]));
printf("%d\n",sizeof(a[0]+1));
printf("%d\n",sizeof(*(a[0]+1)));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(*(a+1)));
printf("%d\n",sizeof(&a[0]+1));
printf("%d\n",sizeof(*(&a[0]+1)));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a[3]));位置不确定,计算值会是个随机值
    printf("%d\n", strlen(&p+1));
//&p是个指向p的指针,类型char**,向后挪动一步的大小是char*
//char*是个指针,指针的大小是4/8
//所以结果是随机值-4/8 (8是64位系统的)
    printf("%d\n", strlen(&p[0]+1));
//5

 对比\0
int a[3][4] = {0};
    printf("%d\n",sizeof(a));
//sizeof中的a代表整个数组,12*4=48
    printf("%d\n",sizeof(a[0][0]));
//第一行第一个元素,4

注意strlen是找\0来结束运算的,而字符串后会有一个隐藏的\0

printf("%d\n",sizeof(a[0]));
//实际上a[0]是第一行一维数组的数组名,那么
//sizeof中的数组名代表整个数组
//而数组名a[0]其实就是个指针,指向4个int
//二维数组中第一行的大小,第一行4个元素
//结果是4*4=16

 练习4:char*p
printf("%d\n",sizeof(a[0]+1));
//a[0]的类型是int[4],作为指针就是int*,指向int
//向后加一的步长是int,指向第一行下标为1的元素
//输出结果是4/8

第一句的意思是,char*指针p指向后面字符串首元素a的地址。

printf("%d\n",sizeof(*(a[0]+1)));
//a[0]+1代表指向 第一行下标为1的元素 的指针
//解引用后代表访问这个元素
//元素类型int,那么结果为4

对比 
printf("%d\n",sizeof(a+1));
//a的类型是int[3][4]
//作为指针的话是一个一维数组int[4]*
//+1的步长是4个int,那么a+1就代表第二行的地址
//结果是4/8
    printf("%d\n",sizeof(*(a+1)));
//*(a+1)=a[1],是第二行的一维数组名
//sizeof中的数组名代表整个数组的大小
//此时的a[1]是第二行,有4个元素
//那就是4个int,16byte

依旧注意p是个指向a的指针,strlen主要看\0的位置。

printf("%d\n",sizeof(&a[0]+1));
//a[0]代表第一行的数组名
//&a[0]就代表指向第一行的指针
//此时的+1步长是 一行数组 的大小
//指针指向第二行的一维数组
//结果4/8
    printf("%d\n",sizeof(*(&a[0]+1)));
//&a[0]+1代表 指向第二行一维数组的指针
//对指针解引用就是a[1]
//结果4*4=16

 练习5:二维数组
printf("%d\n",sizeof(*a));
//a的类型是int[3][4],作为指针是int[4]*
//解引用后是int[4]
//那么*a就代表第一行的数组名
//实际上*a=*(a+0)=a[0]
//那么结果是4*4=16
    printf("%d\n",sizeof(a[3]));
//二维数组一共三行,最大是a[2]
//sizeof中的a[3]结果是这个表达式的类型大小
//所以不涉及越界访问的问题
//直接就是一行元素的大小,16

二维数组的话要明白,二维数组的数组名在作为指针的情况下是个一维数组指针。

int main()
{
    int a[5] = { 1, 2, 3, 4, 5 };
    int *ptr = (int *)(&a + 1);
    printf( "%d,%d", *(a + 1), *(ptr - 1));
    return 0; }
//由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
 int Num;
 char *pcName;
 short sDate;
 char cha[2];
 short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
 printf("%p\n", p + 0x1);
 printf("%p\n", (unsigned long)p + 0x1);
 printf("%p\n", (unsigned int*)p + 0x1);
 return 0; }
还不完全一样
我们得看指针类型,才能确定相邻的指针会差多少字节。
非要说有的话,也只有在按照不同数据进行分配空间后才算是有了类型。
而地址在有了类型后,也就是指针了。
以字节的方式来理解地址和指针

 练习6:判断程序结果
//假设p 的值为0x100000。 如下表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
     printf("%p\n", p + 0x1);
//p+1的步长是20字节 - 十六进制是14
//0x100014
     printf("%p\n", (unsigned long)p + 0x1);
//注意是把p这个结构体指针转换成一个整型了
//所以是加了十六进制的数字1
//结果是0x100001
     printf("%p\n", (unsigned int*)p + 0x1);
//p+1的步长是4或者8字节(32位/64位系统)
//0x100004

首先看*(a+1),a+1代表下标为1的地址,*(a+1)=a[1],结果是2

*(ptr - 1),先分析ptr接收了谁的地址

&a+1 代表 &a是整个数组的地址,+1则是指向a后面一个数组大小的地址

int*强制类型转换后,只能访问一个int大小的地址并存到ptr中,

此时ptr指向a[4]这个元素后面的地址,ptr是int*指针

那么ptr-1的步长是一个int,ptr-1指向a[4],解引用后就是5

output:2,5

练习7:第一眼没看出来的考点(重点看)
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; }

说实话,这个题我做过一遍,第二遍还是没看出来在干嘛

然后我思考了一下原因,就在于我被0x1迷惑了。我一看到0x1完全忘了指针p的作用,只想着地址加1是怎么加啊?

我一直在想地址加1难道不是纯粹的加1吗?这题为啥要这么写?

还真不是!!!

***指针和地址的新理解***

因为你要知道的是其实指针就是地址,地址就是指针

但是还有一个问题,那为什么普通的整型+1就是数值上的+1而不是字节上的+1呢?,因为如果单纯的说地址,那地址是一个储存单元一个地址,相邻的地址会相差1个字节,但是相邻的指针会相差一个字节吗?

那还真不是,地址之间相邻差1byte,所以+1 的步长就是1byte

那么话说回来了,地址会有类型吗? 那还真没有,如果 (int)a (int)a+1
但是并没有看到说对地址分类的说法,所以我建议是 ,也就是说,无论是地址还是字节,在分析+1步长的时候,得 看看它指向的类型占了几个字节来判断。
我觉得这个理解超级对!!以后的我,常回来看看,有新的理解一定要及时改动! 分析题 现在来分析题目,其实题目考察的就是指针类型对于+1步长的影响。
 
 练习8:%x?int+1?(又被击败) 
 
 
 

先分析ptr1指针接收到的地址是谁

&a是整个数组的大小,+1后指向a数组后面 一个数组大小 的空间

int*强制类型转换后就只能访问一个int大小的空间,把这个空间(也就是a[3]后面的int空间)存到了ptr1中

对于非指针+1的步长理解(地址神奇~)

接着是ptr2,首先a是指向数组首元素的,但是被强制转换为了int类型的数据,那么(int)a+1的步长会是多少呢?

int*+1会跳过4个字节,因为指针指向的int占4byte

char*+1跳过1个字节,因为指针指向的char占1byte

类似的,

我们的int+1是多大呢?难道是单纯的1+1=2吗?

我的思考过程是这样

        首先a是数组名,指向首元素的地址,在int强制转换后,是把一个地址强制转换为了int类型。按理说,int+1应该是按照一个int大小来向后挪动的啊!但是这样的话,int*指针又该怎么解释步长是一个int大小呢?

        关于这个问题,其实上个练习也涉及到了。需要知道的是我们在转换为int的时候,是把地址转换为了int类型的数据,也就是说此时的地址不是指向某个数据的指针,而是单纯的数字,其实就是给地址编号加了个数字1,而。

那这时候我们知道了(int)a+1就是a内存中第二个字节的地址,此时把这个地址转换为int*指针,而一个int*指针可以访问4个字节。我们按照小端储存来看看此时的ptr储存的是哪些。

00000002那么 p 指向的就是 a 数组中第一行的前四个元素。a作为指针使用的时候就是int[5]*指针*(a+i)=a[ i ]
010000
00(我加了#,因为没有0x看着别扭)int main() {    int a[3][2] = { (0, 1), (2, 3), (4, 5) };    int *p;    p = a[0];    printf( "%d", p[0]); return 0; }int a[3][2] = { (0, 1), (2, 3), (4, 5) }; int a[3][2] = { 1, 3, 5 };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; }

上面标红的就是ptr指向的内存单元。

那么打印的结果

ptr[-1]是指向ptr指针前面一个int空间的数据,这个空间的数据是a[3]。

按照小端储存的内存中是(低地址)04 00 00 00(高地址)

因为我们把数据存放到内存中按照小端储存,那就是数据的低位放到低地址,高位放到高地址,所以打印出来就是(高位)00 00 00 04 (低位)(省略前面的0)

*ptr则是解引用ptr指针,去通过这个地址访问,访问到的是(低地址)00 00 00 02(高地址)

因为我们把数据存放到内存中按照小端储存,那就是数据的低位放到低地址,高位放到高地址,打印出来的就是(高位)02 00 00 00(低位)(省略前面的0)

看结果a[4]的意思就是a向后挪动了4个步长,每个步长是int[5],那么此时的a[4]已经跳过了4*5=20个int元素

 地址转换为int+1,就是单纯的数字+1啦,实际上就是跳过了一个byte的地址。

练习9:有点陷阱
p[4]就代表p指针向后挪动了4个步长,每个步长是int[4],一共跳过了4*4=16个int元素

这题其实挺搞笑的,要是看走眼就无了

你要注意的是后面是逗号表达式,所以第一行的语句实际上就是

总结:二维数组

a[0]代表二维数组第一行,也就是1和3两个元素。

我们的int*p指向指向了a[0]这个一维数组,也就是说我们指向了第一行int[2]大小的首元素地址,因为p是int*指针,便一次指向一个int大小的空间,也就是指向首元素1。

而打印中的p[0]实际上就等价于*(p+0),也就是首元素1。

练习10:二维数组指针运用、指针运算、%p打印整型
p [4][2]=* ( * ( p + 4 ) + 2 );   

a的类型是int[5][5]二维数组,我们的p是int[4]*类型的指针,指向int[4]。

此时我们把p=a,而 a 没有被&也没有在sizeof当中,所以是数组首元素的地址,也就是第一行

因为地址无所谓正负,所以%p认为内存中存的就是一个地址,直接按照内存中的补码输出。

接着分析&p[4][2]和&a[4][2]什么意思

        首先我们知道p是指向a数组中第一行前四个元素的指针,是一个int[4]*的指针。

        a我们说是int[5][5]类型,而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; },那么此时a+1的步长就会是一个int[5]的大小,而我们又知道ptr1指向的就是aa数组最后一个元素后面的一个int大小的空间,代表a这个指针向后挪动了 i 步并访问这个空间的元素。那么int main() { char *a[] = {"work","at","alibaba"}; char**pa = a; pa++; printf("%s\n", *pa); return 0; }。此时的a[4]是一个int[5]类型的数组,把a[4]看做这个int[5]的数组名,那么a[4]数组名在作为指针使用的时候就是int*指针,那么a[4][2]就代表访问了a[4]数组中下标为2的元素。

        同样的道理,p是一个int[4]*的指针,我们又知道p的地址是a数组首元素的地址,指向a数组第一行的前四个元素,那么它+1的步长是int[4],"work" 字符串  是一堆char类型数据的集合,也就是char[5]数组,此时的p[4]就是一个int[4]类型的数组名,而p[4]数组名在作为指针使用的时候就是int*指针,p[4][2]就代表访问了p[4]数组中下标为2的元素。

“work”字符串就相当于char[5]的数组名,

数组名在运用的时候会变为指针,也就是会从char[5]数组变为char*指针(注:sizeof和&中的数组名除外)

我画个图,配合来看。

此时p[4][2]和a[4][2]的地址相4个int,也就是16个byte。

要知道 &p[4][2] - &a[4][2]是指针之间的元素个数,又因为p的地址比a的地址低,那么%d的形式打印出来就是-4,而%p的话则需要把-4的补码写出来,按照16进制数字进行输出。

那么为什么%p就不用像%d一样进行原码转换输出,而是直接使用补码呢?

数组名“work”作为指针的时候是指向首元素的,也就是指向w这个字符

这里我们演示一下

要达到,一眼能看出来代码在说什么的水平,这题就算OK了。

放一下结果

练习11:二维数组
当我们在char*[ ]数组中存放“work”字符串的时候,是存放了字符w的地址。

这题比较简单。

首先&aa是取出来了整个二维数组大小的地址,向后+1的步长是一个二维数组的大小,转换为int*指针便只能访问一个int,那么如图。

*(ptr-1)就是指ptr指针向前挪了一个int大小,正好指向数组最后一个元素,然后访问住户,那么结果就是10。

同样的道理,

aa是int[2][5]数组,在作为指针运用的时候是一个int[5]*指针,指向第一行的一维int[5]数组,+1的步长就是指向第二行的一维数组int[5],加上*解引用代表访问这个数组,那么也就相当于aa[1]。

实际上也可直接利用*(a+i)=a[ i ],上面只是详细解释了一下。

aa[1]的类型是int[5],此时被强制转化为int*指针,那便只能指向一个int大小的空间,也就是指向a[1][0],那么ptr2就是指向了a[1][0]。

*(ptr2-1)就是向前挪了一个int大小,指向a[0][4]元素,*代表访问元素,结果就是5。

如图。 

练习12:char*a[ ]
 char**pa = a;

        首先我们要理解char*a[ ]初始化意味着什么,要知道数组中存放的是char*类型的数据,而我们初始化的时候放的是字符串,那么字符串又意味着什么?

        可以看到  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; }。那么 char *c[] = {"ENTER","NEW","POINT","FIRST"}; char**cp[] = {c+3,c+2,c+1,c};而我们又知道,char**[ ]则代表我们存放的数据是char**这个二级指针数据。所以实际上我们存放的就是char*类型的数据,还需要知道的是数组名c存放了首元素char*的地址。

        也就是说,实际上c,c+1,c+2,c+3就是char**类型的数据

c,c+1,c+2,c+3便分别指向c数组中下标为0,1,2,3,4,的元素

而这些元素的类型都是char*类型,

上面这个语句的意思是,现在我们用pa指针指向a数组中首元素的地址,指向的数据类型是char*。

也就是说指向第一个char*。如图。

         pa++是指向第二个char*的指针,现在对指针进行*解引用,也就是访问这个char*元素,而我们在打印的时候需要传递的就是char*数据,这个char*指向字符串“at”当中'a'的地址,所以打印的结果是at。

练习13:char***之终极练习
c,c+1,c+2,c+3这四个char**元素,分别存放了下标为0,1,2,3,4,的char*元素的地址。(指向谁,存放谁的地址)

 首先梳理第一句的意思,我们在上面已经分析过char*[ ]当中存放字符串意味着什么,这里便只放图了。

 存放了四个字符串首元素的地址。

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);,那么我们又知道c是char*[4]数组,是一个数组名,而数组名在运用的时候就会变为指向首元素的指针,也就是从char*[4]类型变为char**指针,代表指向char*数据的指针,也就是 *++cpp=c+2。所以 printf("%s\n", **++cpp); printf("%s\n", *--*++cpp+3); printf("%s\n", *cpp[-2]+3); printf("%s\n", cpp[-1][-1]+1);,又因为c是指向数组首元素的,那么c+1,c+2,c+3便是指针c向后移动的步数,c是一个char**指针,移动的步长是char*,那么此时的 第二句的cpp是上一句的++cpp,是指向c+2的。总体过程是:*--*++cpp + 3 = *--(c+1)+ 3 = *c+3 = "ENTER" + 3 = "ER"也就是 "ENTER"(字符串数组名,实际上是首元素'E'的地址,类型char*)

 

接着是第三句。

 printf("%s\n", **++cpp);
 printf("%s\n", *--*++cpp+3);
 printf("%s\n", *cpp[-2]+3);
 printf("%s\n", cpp[-1][-1]+1);
 char***是指向char**数据的指针,此时的cp是指向首元素c+3这个char**的char***指针,把cp的地址交给cpp,也就代表cpp和cp一样,是指向首元素c+3这个char**的char***指针,也就是存放了c+3这个char**的地址。
*cpp[-2]+3 = *(*(cpp-2))+3 = *(c+3)+3 = "FIRST" + 3 = "ST"

先分析第一句,++cpp代表cpp向后挪动了一步,这一步的大小是一个char**。++cpp的类型是char***,对++cpp进行解引用,*++cpp就是char**类型,也就是在访问++cpp所指向的对象,此时

++cpp指向c+2,那么"FIRST"(字符串数组名,实际上是首元素'F'的地址,类型char*)

再进行解引用的话,就 相当于对c+2进行解引用,c+2指向的对象是  "POINT" 的 首字符 'P' 地址,对c+2进行解引用代表访问  c+2所指向的对象,也就是访问"POINT" 的 首字符 'P' 地址,此时把这个地址用%s的形式打印, 那么便是POINT。 如图。

 现在分析第二句。

 printf("%s\n", **++cpp);
 printf("%s\n", *--*++cpp+3);
 printf("%s\n", *cpp[-2]+3);
 printf("%s\n", cpp[-1][-1]+1);

 我们需要知道的是前置后置++和--都会对数据本身造成影响,也就是说cpp[-1][-1]+1 = *(*(cpp-1)-1)+ 1 = *((c+2)-1)+1 = *(c+1)+1 = "NEW"+1=其次,我们要先运算*--*++cpp+3中的++cpp。那么++cpp指向c+1,解引用的话就是访问++cpp指向的对象,++cpp指向c+1,那么*++cpp=c+1,然后我们再对c+1进行前置--,那么c+1就会变为c。再对c进行解引用,就是在访问c所指向的对象,c指向ENTER字符串中E的地址,再对这个地址+3就意味着,从字符E向后走了3步,每步大小是一个字符char,那么打印结果是ER。

"EW" "NEW"(字符串数组名,实际上是首元素'N'的地址,类型char*)

 分析第三句

直接写出整体运算过程
此时的cpp指向cp数组中第三个元素,(不再细说,道理想通) 分析最后一句
 
 

因为上一句并不是使用的前置后置++或者--,所以cpp还是指向cp数组中第三个元素。

 这题达到能[+++]的水平就可以了。

练习到此,后续还会有练习,不过这算是比较深入的练习了,如果我没解释清楚,可以看我之前对于指针文章的解释,或者直接私信我,很乐意解答,可惜没人看哈哈哈

)
File: /www/wwwroot/outofmemory.cn/tmp/route_read.php, Line: 126, InsideLink()
File: /www/wwwroot/outofmemory.cn/tmp/index.inc.php, Line: 166, include(/www/wwwroot/outofmemory.cn/tmp/route_read.php)
File: /www/wwwroot/outofmemory.cn/index.php, Line: 30, include(/www/wwwroot/outofmemory.cn/tmp/index.inc.php)
从0开始学c语言-30- 指针不练习?还真觉得自己会了~_C_内存溢出

从0开始学c语言-30- 指针不练习?还真觉得自己会了~

从0开始学c语言-30- 指针不练习?还真觉得自己会了~,第1张

上一篇:从0开始学c语言-29-数据储存练习、筑基期练习、两步翻转_阿秋的阿秋不是阿秋的博客-CSDN博客

应有的基础:

从0开始学c语言-28-qsort函数、 数组和指针参数、函数指针数组(转移表)、回调函数_阿秋的阿秋不是阿秋的博客-CSDN博客

从0开始学c语言-27-字符指针,指针数组和数组指针_阿秋的阿秋不是阿秋的博客-CSDN博客

目录

练习1:int arr[ ]

 练习2:char arr[ ] zifu

 对比

 练习3:char arr[ ]

 对比\0

 练习4:char*p

对比 

 练习5:二维数组

 练习6:判断程序结果

练习7:第一眼没看出来的考点(重点看)

***指针和地址的新理解***

分析题

练习8:%x?int+1?(又被击败)

对于非指针+1的步长理解(地址神奇~)

练习9:有点陷阱

练习10:二维数组指针运用、指针运算、%p打印整型

练习11:二维数组

练习12:char*a[ ]

练习13:char***之终极练习


练习1:int arr[ ]

应有的知识:

数组名的意义: 1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。 2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。 3. 除此之外所有的数组名都表示首元素的地址。
//一维数组
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a+0));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(a[1]));
printf("%d\n",sizeof(&a));
printf("%d\n",sizeof(*&a));
printf("%d\n",sizeof(&a+1));
printf("%d\n",sizeof(&a[0]));
printf("%d\n",sizeof(&a[0]+1));

请思考每个printf函数输出的结果是多少。

开始讲

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));

a单纯的数组名实际上就是数组首元素的地址,那么地址的大小在32位系统上是4byte,在64位系统上是8byte大小。但是呢,sizeof当中的数组名则代表整个数组,这一点要特别注意,那么a数组的大小就是4个int的大小,也就是16byte。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a+0));

实际上地址就是指针,那么a+0就代表a这个指针向后挪动了0步。且a本身指向数组首元素,+0表示一步没有挪动。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(*a));

我们知道a是指针,*a就是对指针进行解引用,a指向数组首元素,那么*a就是在访问数组首元素,而数组首元素的类型是int,int的大小是4byte。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a+1));

a+0和a+1都是指针,只不过一个指向首元素,一个指向下标为1的元素。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a[1]));

a[1]代表数组下标为1的元素,而这个元素的类型是int,那么就是4byte。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(&a));

我们早在前面说过,

1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。 2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
那么现在是sizeof和&数组名组合到一起,这时候,sizeof当中的&数组名,则是计算的 【指向整个数组的指针】的大小。
printf("%d\n",sizeof(*&a));

现在在&a前面加上了*, 而*&a=a,a又在sizeof当中,那就代表我们访问了整个数组,应该是16byte。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(&a+1));

&a代表指向整个数组的指针,那么+1则会让指针向后挪动一步,这一步的大小是整个数组的大小,我们计算的还是 指针的大小。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(&a[0]));

&a[0]是什么意思?

如果讲结合性的话,我还不太熟练,只能放图监视给你看

 也就是说&a[0]=&(a[0])

意思是取出数组第一个元素的地址,本质上是个指针。
printf("%d\n",sizeof(&a[0]+1));

那么同样的,&a[0]+1就代表指向第一个元素的指针向后走了一步,而这一步的大小是一个int,也就是指向了下标为1的元素。

现在看看结果,是否符合我们的推理。

 练习2:char arr[ ] zifu
char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));
	printf("%d\n", sizeof(arr + 0));
	printf("%d\n", sizeof(*arr));
	printf("%d\n", sizeof(arr[1]));
	printf("%d\n", sizeof(&arr));
	printf("%d\n", sizeof(&arr + 1));
	printf("%d\n", sizeof(&arr[0] + 1));

实际上,这题和上面那道题几乎一模一样。我就把解释写在代码里了哈。

char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));
//整个数组的大小,6
	printf("%d\n", sizeof(arr + 0));
//指向下标1的指针,4/8 ,8代表64位系统的结果
	printf("%d\n", sizeof(*arr));
//访问数组第一个元素,1
	printf("%d\n", sizeof(arr[1]));
//访问数组第一个元素,1
	printf("%d\n", sizeof(&arr));
//指向整个数组的指针,4/8
	printf("%d\n", sizeof(&arr + 1));
//指向整个数组的指针向后挪动一步,4/8
	printf("%d\n", sizeof(&arr[0] + 1));
//指向数组第一个元素的指针向后挪动一步,4/8

 对比
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));

实际上大体也是一样的,只不过我们换成了strlen,那就要特别注意\0。

char arr[] = { 'a','b','c','d','e','f' };
    printf("%d\n", strlen(arr));
//没有char arr[] = "abcdef";
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr+0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr+1));
printf("%d\n", sizeof(&arr[0]+1));,随机值
    printf("%d\n", strlen(arr+0));
//没有char arr[] = "abcdef";
    printf("%d\n", sizeof(arr));
//sizeof中的数组名代表整个数组,7(记得有隐藏的char arr[] = "abcdef";
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));元素)
    printf("%d\n", sizeof(arr+0));
//arr+0代表指针,指向第一个元素,4/8 (8是64位系统的结果)
    printf("%d\n", sizeof(*arr));
//代表字符串的第一个字符,类型char,1
    printf("%d\n", sizeof(arr[1]));
//代表字符串的第一个字符,类型char,1
    printf("%d\n", sizeof(&arr));
//指向整个char数组的指针,4/8
    printf("%d\n", sizeof(&arr+1));
//指向整个char数组的指针向后挪一步,4/8
    printf("%d\n", sizeof(&arr[0]+1));
//指向字符串第一个字符的指针向后挪一步,4/8,随机值
    printf("%d\n", strlen(*arr));
//*arr不是指针,不能计算
    printf("%d\n", strlen(arr[1]));
//arr[1]不是指针,不能计算
    printf("%d\n", strlen(&arr));
//没有char arr[] = "abcdef";
    printf("%d\n", strlen(arr));
//6
    printf("%d\n", strlen(arr+0));
//6
    printf("%d\n", strlen(*arr));
//不能计算,strlen要的是const char*指针类型的数据
    printf("%d\n", strlen(arr[1]));
//不能计算,strlen要的是const char*指针类型的数据
//虽然后面这两个语句不是const char*类型,但是可以把地址传过去
    printf("%d\n", strlen(&arr));
//6
    printf("%d\n", strlen(&arr+1));
//指向数组的指针向后挪动一步
//这一步的大小是一个数组的大小,打印结果随机值
    printf("%d\n", strlen(&arr[0]+1));
//指向数组中首元素的指针向后挪一步
//这步大小是一个char的大小,打印结果是5,随机值
    printf("%d\n", strlen(&arr+1));
//没有char *p = "abcdef";
printf("%d\n", sizeof(p));
printf("%d\n", sizeof(p+1));
printf("%d\n", sizeof(*p));
printf("%d\n", sizeof(p[0]));
printf("%d\n", sizeof(&p));
printf("%d\n", sizeof(&p+1));
printf("%d\n", sizeof(&p[0]+1));,随机值-6  因为是指向整个数组的指针向后挪一步
    printf("%d\n", strlen(&arr[0]+1));
//没有char *p = "abcdef";
    printf("%d\n", sizeof(p));
//p是个指针,4/8 (8是64位系统的结果)
    printf("%d\n", sizeof(p+1));
//p+1指向下一个字符c,4/8
    printf("%d\n", sizeof(*p));
//*p代表首字符字符a,1
    printf("%d\n", sizeof(p[0]));
//代表首字符字符a,1
    printf("%d\n", sizeof(&p));
//&p代表指针p的地址,4/8
    printf("%d\n", sizeof(&p+1));
//指向指针p的指针向后挪动一步,这一步大小是一个char*,4/8
    printf("%d\n", sizeof(&p[0]+1));
//指向首字符的指针向后挪动一步,这一步的大小是一个char,4/8,随机值-1 因为是指向一个元素的指针向后挪一步

很多警告,主要看类型不同的警告,正如我们所说,*arr和arr[0]是不能计算的,

在注释掉后,计算的结果就像我们注释里说的。

 练习3:char arr[ ]
char *p = "abcdef";
printf("%d\n", strlen(p));
printf("%d\n", strlen(p+1));
printf("%d\n", strlen(*p));
printf("%d\n", strlen(p[0]));
printf("%d\n", strlen(&p));
printf("%d\n", strlen(&p+1));
printf("%d\n", strlen(&p[0]+1));

要知道的是,储存字符串当中的字符数组名是第一个字符的地址。

char *p = "abcdef";
    printf("%d\n", strlen(p));
//6
    printf("%d\n", strlen(p+1));
//5
    printf("%d\n", strlen(*p));
    printf("%d\n", strlen(p[0]));
//上面的*p和p[0]是char类型,不能计算
    printf("%d\n", strlen(&p));
//p此时是个指向首元素的指针,而&p代表指向p的指针
//那么&p中保存的是p的地址,int a[3][4] = {0};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a[0][0]));
printf("%d\n",sizeof(a[0]));
printf("%d\n",sizeof(a[0]+1));
printf("%d\n",sizeof(*(a[0]+1)));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(*(a+1)));
printf("%d\n",sizeof(&a[0]+1));
printf("%d\n",sizeof(*(&a[0]+1)));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a[3]));位置不确定,计算值会是个随机值
    printf("%d\n", strlen(&p+1));
//&p是个指向p的指针,类型char**,向后挪动一步的大小是char*
//char*是个指针,指针的大小是4/8
//所以结果是随机值-4/8 (8是64位系统的)
    printf("%d\n", strlen(&p[0]+1));
//5

 对比\0
int a[3][4] = {0};
    printf("%d\n",sizeof(a));
//sizeof中的a代表整个数组,12*4=48
    printf("%d\n",sizeof(a[0][0]));
//第一行第一个元素,4

注意strlen是找\0来结束运算的,而字符串后会有一个隐藏的\0

printf("%d\n",sizeof(a[0]));
//实际上a[0]是第一行一维数组的数组名,那么
//sizeof中的数组名代表整个数组
//而数组名a[0]其实就是个指针,指向4个int
//二维数组中第一行的大小,第一行4个元素
//结果是4*4=16

 练习4:char*p
printf("%d\n",sizeof(a[0]+1));
//a[0]的类型是int[4],作为指针就是int*,指向int
//向后加一的步长是int,指向第一行下标为1的元素
//输出结果是4/8

第一句的意思是,char*指针p指向后面字符串首元素a的地址。

printf("%d\n",sizeof(*(a[0]+1)));
//a[0]+1代表指向 第一行下标为1的元素 的指针
//解引用后代表访问这个元素
//元素类型int,那么结果为4

对比 
printf("%d\n",sizeof(a+1));
//a的类型是int[3][4]
//作为指针的话是一个一维数组int[4]*
//+1的步长是4个int,那么a+1就代表第二行的地址
//结果是4/8
    printf("%d\n",sizeof(*(a+1)));
//*(a+1)=a[1],是第二行的一维数组名
//sizeof中的数组名代表整个数组的大小
//此时的a[1]是第二行,有4个元素
//那就是4个int,16byte

依旧注意p是个指向a的指针,strlen主要看\0的位置。

printf("%d\n",sizeof(&a[0]+1));
//a[0]代表第一行的数组名
//&a[0]就代表指向第一行的指针
//此时的+1步长是 一行数组 的大小
//指针指向第二行的一维数组
//结果4/8
    printf("%d\n",sizeof(*(&a[0]+1)));
//&a[0]+1代表 指向第二行一维数组的指针
//对指针解引用就是a[1]
//结果4*4=16

 练习5:二维数组
printf("%d\n",sizeof(*a));
//a的类型是int[3][4],作为指针是int[4]*
//解引用后是int[4]
//那么*a就代表第一行的数组名
//实际上*a=*(a+0)=a[0]
//那么结果是4*4=16
    printf("%d\n",sizeof(a[3]));
//二维数组一共三行,最大是a[2]
//sizeof中的a[3]结果是这个表达式的类型大小
//所以不涉及越界访问的问题
//直接就是一行元素的大小,16

二维数组的话要明白,二维数组的数组名在作为指针的情况下是个一维数组指针。

int main()
{
    int a[5] = { 1, 2, 3, 4, 5 };
    int *ptr = (int *)(&a + 1);
    printf( "%d,%d", *(a + 1), *(ptr - 1));
    return 0; }
//由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
 int Num;
 char *pcName;
 short sDate;
 char cha[2];
 short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
 printf("%p\n", p + 0x1);
 printf("%p\n", (unsigned long)p + 0x1);
 printf("%p\n", (unsigned int*)p + 0x1);
 return 0; }
还不完全一样
我们得看指针类型,才能确定相邻的指针会差多少字节。
非要说有的话,也只有在按照不同数据进行分配空间后才算是有了类型。
而地址在有了类型后,也就是指针了。
以字节的方式来理解地址和指针

 练习6:判断程序结果
//假设p 的值为0x100000。 如下表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
     printf("%p\n", p + 0x1);
//p+1的步长是20字节 - 十六进制是14
//0x100014
     printf("%p\n", (unsigned long)p + 0x1);
//注意是把p这个结构体指针转换成一个整型了
//所以是加了十六进制的数字1
//结果是0x100001
     printf("%p\n", (unsigned int*)p + 0x1);
//p+1的步长是4或者8字节(32位/64位系统)
//0x100004

首先看*(a+1),a+1代表下标为1的地址,*(a+1)=a[1],结果是2

*(ptr - 1),先分析ptr接收了谁的地址

&a+1 代表 &a是整个数组的地址,+1则是指向a后面一个数组大小的地址

int*强制类型转换后,只能访问一个int大小的地址并存到ptr中,

此时ptr指向a[4]这个元素后面的地址,ptr是int*指针

那么ptr-1的步长是一个int,ptr-1指向a[4],解引用后就是5

output:2,5

练习7:第一眼没看出来的考点(重点看)
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; }

说实话,这个题我做过一遍,第二遍还是没看出来在干嘛

然后我思考了一下原因,就在于我被0x1迷惑了。我一看到0x1完全忘了指针p的作用,只想着地址加1是怎么加啊?

我一直在想地址加1难道不是纯粹的加1吗?这题为啥要这么写?

还真不是!!!

***指针和地址的新理解***

因为你要知道的是其实指针就是地址,地址就是指针

但是还有一个问题,那为什么普通的整型+1就是数值上的+1而不是字节上的+1呢?,因为如果单纯的说地址,那地址是一个储存单元一个地址,相邻的地址会相差1个字节,但是相邻的指针会相差一个字节吗?

那还真不是,地址之间相邻差1byte,所以+1 的步长就是1byte

那么话说回来了,地址会有类型吗? 那还真没有,如果 (int)a (int)a+1
但是并没有看到说对地址分类的说法,所以我建议是 ,也就是说,无论是地址还是字节,在分析+1步长的时候,得 看看它指向的类型占了几个字节来判断。
我觉得这个理解超级对!!以后的我,常回来看看,有新的理解一定要及时改动! 分析题 现在来分析题目,其实题目考察的就是指针类型对于+1步长的影响。
 
 练习8:%x?int+1?(又被击败) 
 
 
 

先分析ptr1指针接收到的地址是谁

&a是整个数组的大小,+1后指向a数组后面 一个数组大小 的空间

int*强制类型转换后就只能访问一个int大小的空间,把这个空间(也就是a[3]后面的int空间)存到了ptr1中

对于非指针+1的步长理解(地址神奇~)

接着是ptr2,首先a是指向数组首元素的,但是被强制转换为了int类型的数据,那么(int)a+1的步长会是多少呢?

int*+1会跳过4个字节,因为指针指向的int占4byte

char*+1跳过1个字节,因为指针指向的char占1byte

类似的,

我们的int+1是多大呢?难道是单纯的1+1=2吗?

我的思考过程是这样

        首先a是数组名,指向首元素的地址,在int强制转换后,是把一个地址强制转换为了int类型。按理说,int+1应该是按照一个int大小来向后挪动的啊!但是这样的话,int*指针又该怎么解释步长是一个int大小呢?

        关于这个问题,其实上个练习也涉及到了。需要知道的是我们在转换为int的时候,是把地址转换为了int类型的数据,也就是说此时的地址不是指向某个数据的指针,而是单纯的数字,其实就是给地址编号加了个数字1,而。

那这时候我们知道了(int)a+1就是a内存中第二个字节的地址,此时把这个地址转换为int*指针,而一个int*指针可以访问4个字节。我们按照小端储存来看看此时的ptr储存的是哪些。

00000002那么 p 指向的就是 a 数组中第一行的前四个元素。a作为指针使用的时候就是int[5]*指针*(a+i)=a[ i ]
010000
00(我加了#,因为没有0x看着别扭)int main() {    int a[3][2] = { (0, 1), (2, 3), (4, 5) };    int *p;    p = a[0];    printf( "%d", p[0]); return 0; }int a[3][2] = { (0, 1), (2, 3), (4, 5) }; int a[3][2] = { 1, 3, 5 };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; }

上面标红的就是ptr指向的内存单元。

那么打印的结果

ptr[-1]是指向ptr指针前面一个int空间的数据,这个空间的数据是a[3]。

按照小端储存的内存中是(低地址)04 00 00 00(高地址)

因为我们把数据存放到内存中按照小端储存,那就是数据的低位放到低地址,高位放到高地址,所以打印出来就是(高位)00 00 00 04 (低位)(省略前面的0)

*ptr则是解引用ptr指针,去通过这个地址访问,访问到的是(低地址)00 00 00 02(高地址)

因为我们把数据存放到内存中按照小端储存,那就是数据的低位放到低地址,高位放到高地址,打印出来的就是(高位)02 00 00 00(低位)(省略前面的0)

看结果a[4]的意思就是a向后挪动了4个步长,每个步长是int[5],那么此时的a[4]已经跳过了4*5=20个int元素

 地址转换为int+1,就是单纯的数字+1啦,实际上就是跳过了一个byte的地址。

练习9:有点陷阱
p[4]就代表p指针向后挪动了4个步长,每个步长是int[4],一共跳过了4*4=16个int元素

这题其实挺搞笑的,要是看走眼就无了

你要注意的是后面是逗号表达式,所以第一行的语句实际上就是

总结:二维数组

a[0]代表二维数组第一行,也就是1和3两个元素。

我们的int*p指向指向了a[0]这个一维数组,也就是说我们指向了第一行int[2]大小的首元素地址,因为p是int*指针,便一次指向一个int大小的空间,也就是指向首元素1。

而打印中的p[0]实际上就等价于*(p+0),也就是首元素1。

练习10:二维数组指针运用、指针运算、%p打印整型
p [4][2]=* ( * ( p + 4 ) + 2 );   

a的类型是int[5][5]二维数组,我们的p是int[4]*类型的指针,指向int[4]。

此时我们把p=a,而 a 没有被&也没有在sizeof当中,所以是数组首元素的地址,也就是第一行

因为地址无所谓正负,所以%p认为内存中存的就是一个地址,直接按照内存中的补码输出。

接着分析&p[4][2]和&a[4][2]什么意思

        首先我们知道p是指向a数组中第一行前四个元素的指针,是一个int[4]*的指针。

        a我们说是int[5][5]类型,而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; },那么此时a+1的步长就会是一个int[5]的大小,而我们又知道ptr1指向的就是aa数组最后一个元素后面的一个int大小的空间,代表a这个指针向后挪动了 i 步并访问这个空间的元素。那么int main() { char *a[] = {"work","at","alibaba"}; char**pa = a; pa++; printf("%s\n", *pa); return 0; }。此时的a[4]是一个int[5]类型的数组,把a[4]看做这个int[5]的数组名,那么a[4]数组名在作为指针使用的时候就是int*指针,那么a[4][2]就代表访问了a[4]数组中下标为2的元素。

        同样的道理,p是一个int[4]*的指针,我们又知道p的地址是a数组首元素的地址,指向a数组第一行的前四个元素,那么它+1的步长是int[4],"work" 字符串  是一堆char类型数据的集合,也就是char[5]数组,此时的p[4]就是一个int[4]类型的数组名,而p[4]数组名在作为指针使用的时候就是int*指针,p[4][2]就代表访问了p[4]数组中下标为2的元素。

“work”字符串就相当于char[5]的数组名,

数组名在运用的时候会变为指针,也就是会从char[5]数组变为char*指针(注:sizeof和&中的数组名除外)

我画个图,配合来看。

此时p[4][2]和a[4][2]的地址相4个int,也就是16个byte。

要知道 &p[4][2] - &a[4][2]是指针之间的元素个数,又因为p的地址比a的地址低,那么%d的形式打印出来就是-4,而%p的话则需要把-4的补码写出来,按照16进制数字进行输出。

那么为什么%p就不用像%d一样进行原码转换输出,而是直接使用补码呢?

数组名“work”作为指针的时候是指向首元素的,也就是指向w这个字符

这里我们演示一下

要达到,一眼能看出来代码在说什么的水平,这题就算OK了。

放一下结果

练习11:二维数组
当我们在char*[ ]数组中存放“work”字符串的时候,是存放了字符w的地址。

这题比较简单。

首先&aa是取出来了整个二维数组大小的地址,向后+1的步长是一个二维数组的大小,转换为int*指针便只能访问一个int,那么如图。

*(ptr-1)就是指ptr指针向前挪了一个int大小,正好指向数组最后一个元素,然后访问住户,那么结果就是10。

同样的道理,

aa是int[2][5]数组,在作为指针运用的时候是一个int[5]*指针,指向第一行的一维int[5]数组,+1的步长就是指向第二行的一维数组int[5],加上*解引用代表访问这个数组,那么也就相当于aa[1]。

实际上也可直接利用*(a+i)=a[ i ],上面只是详细解释了一下。

aa[1]的类型是int[5],此时被强制转化为int*指针,那便只能指向一个int大小的空间,也就是指向a[1][0],那么ptr2就是指向了a[1][0]。

*(ptr2-1)就是向前挪了一个int大小,指向a[0][4]元素,*代表访问元素,结果就是5。

如图。 

练习12:char*a[ ]
 char**pa = a;

        首先我们要理解char*a[ ]初始化意味着什么,要知道数组中存放的是char*类型的数据,而我们初始化的时候放的是字符串,那么字符串又意味着什么?

        可以看到  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; }。那么 char *c[] = {"ENTER","NEW","POINT","FIRST"}; char**cp[] = {c+3,c+2,c+1,c};而我们又知道,char**[ ]则代表我们存放的数据是char**这个二级指针数据。所以实际上我们存放的就是char*类型的数据,还需要知道的是数组名c存放了首元素char*的地址。

        也就是说,实际上c,c+1,c+2,c+3就是char**类型的数据

c,c+1,c+2,c+3便分别指向c数组中下标为0,1,2,3,4,的元素

而这些元素的类型都是char*类型,

上面这个语句的意思是,现在我们用pa指针指向a数组中首元素的地址,指向的数据类型是char*。

也就是说指向第一个char*。如图。

         pa++是指向第二个char*的指针,现在对指针进行*解引用,也就是访问这个char*元素,而我们在打印的时候需要传递的就是char*数据,这个char*指向字符串“at”当中'a'的地址,所以打印的结果是at。

练习13:char***之终极练习
c,c+1,c+2,c+3这四个char**元素,分别存放了下标为0,1,2,3,4,的char*元素的地址。(指向谁,存放谁的地址)

 首先梳理第一句的意思,我们在上面已经分析过char*[ ]当中存放字符串意味着什么,这里便只放图了。

 存放了四个字符串首元素的地址。

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);,那么我们又知道c是char*[4]数组,是一个数组名,而数组名在运用的时候就会变为指向首元素的指针,也就是从char*[4]类型变为char**指针,代表指向char*数据的指针,也就是 *++cpp=c+2。所以 printf("%s\n", **++cpp); printf("%s\n", *--*++cpp+3); printf("%s\n", *cpp[-2]+3); printf("%s\n", cpp[-1][-1]+1);,又因为c是指向数组首元素的,那么c+1,c+2,c+3便是指针c向后移动的步数,c是一个char**指针,移动的步长是char*,那么此时的 第二句的cpp是上一句的++cpp,是指向c+2的。总体过程是:*--*++cpp + 3 = *--(c+1)+ 3 = *c+3 = "ENTER" + 3 = "ER"也就是 "ENTER"(字符串数组名,实际上是首元素'E'的地址,类型char*)

 

接着是第三句。

 printf("%s\n", **++cpp);
 printf("%s\n", *--*++cpp+3);
 printf("%s\n", *cpp[-2]+3);
 printf("%s\n", cpp[-1][-1]+1);
 char***是指向char**数据的指针,此时的cp是指向首元素c+3这个char**的char***指针,把cp的地址交给cpp,也就代表cpp和cp一样,是指向首元素c+3这个char**的char***指针,也就是存放了c+3这个char**的地址。
*cpp[-2]+3 = *(*(cpp-2))+3 = *(c+3)+3 = "FIRST" + 3 = "ST"

先分析第一句,++cpp代表cpp向后挪动了一步,这一步的大小是一个char**。++cpp的类型是char***,对++cpp进行解引用,*++cpp就是char**类型,也就是在访问++cpp所指向的对象,此时

++cpp指向c+2,那么"FIRST"(字符串数组名,实际上是首元素'F'的地址,类型char*)

再进行解引用的话,就 相当于对c+2进行解引用,c+2指向的对象是  "POINT" 的 首字符 'P' 地址,对c+2进行解引用代表访问  c+2所指向的对象,也就是访问"POINT" 的 首字符 'P' 地址,此时把这个地址用%s的形式打印, 那么便是POINT。 如图。

 现在分析第二句。

 printf("%s\n", **++cpp);
 printf("%s\n", *--*++cpp+3);
 printf("%s\n", *cpp[-2]+3);
 printf("%s\n", cpp[-1][-1]+1);

 我们需要知道的是前置后置++和--都会对数据本身造成影响,也就是说cpp[-1][-1]+1 = *(*(cpp-1)-1)+ 1 = *((c+2)-1)+1 = *(c+1)+1 = "NEW"+1=其次,我们要先运算*--*++cpp+3中的++cpp。那么++cpp指向c+1,解引用的话就是访问++cpp指向的对象,++cpp指向c+1,那么*++cpp=c+1,然后我们再对c+1进行前置--,那么c+1就会变为c。再对c进行解引用,就是在访问c所指向的对象,c指向ENTER字符串中E的地址,再对这个地址+3就意味着,从字符E向后走了3步,每步大小是一个字符char,那么打印结果是ER。

"EW" "NEW"(字符串数组名,实际上是首元素'N'的地址,类型char*)

 分析第三句

直接写出整体运算过程
此时的cpp指向cp数组中第三个元素,(不再细说,道理想通) 分析最后一句
 
 

因为上一句并不是使用的前置后置++或者--,所以cpp还是指向cp数组中第三个元素。

 这题达到能的水平就可以了。

练习到此,后续还会有练习,不过这算是比较深入的练习了,如果我没解释清楚,可以看我之前对于指针文章的解释,或者直接私信我,很乐意解答,可惜没人看哈哈哈

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/3002183.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-09-27
下一篇 2022-09-27

发表评论

登录后才能评论

评论列表(0条)

保存