上一篇:从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[ ] zifuchar 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
练习7:第一眼没看出来的考点(重点看)output:2,5
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
那么话说回来了,地址会有类型吗? 那还真没有,如果但是并没有看到说对地址分类的说法,所以我建议是我觉得这个理解超级对!!以后的我,常回来看看,有新的理解一定要及时改动! 分析题 现在来分析题目,其实题目考察的就是指针类型对于+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储存的是哪些。
01 | 0000000200 | 00 | ||
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; } | 那么 p 指向的就是 a 数组中第一行的前四个元素。a作为指针使用的时候就是int[5]*指针*(a+i)=a[ i ]
上面标红的就是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元素
练习9:有点陷阱地址转换为int+1,就是单纯的数字+1啦,实际上就是跳过了一个byte的地址。
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数组中第三个元素。
这题达到能的水平就可以了。
练习到此,后续还会有练习,不过这算是比较深入的练习了,如果我没解释清楚,可以看我之前对于指针文章的解释,或者直接私信我,很乐意解答,可惜没人看哈哈哈
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)