笔记19-3(C语言进阶 指针进阶 练习)

目录

注:

复习 数组名的意义

例1 求打印结果(sizeof 求int a[]各种有关元素)

例2 求打印结果(sizeof/strlen 求char []各种有关元素)

Part 1(sizeof { 'a', 'b', 'c', 'd','e', 'f' })

Part 2(strlen { 'a', 'b', 'c', 'd','e', 'f' })

Part 3(sizeof "abcdef")

Part 4(strlen "abcdef")

例3 求打印结果(sizeof/strlen 求char* []各种有关元素)

Part 1(sizeof "abcdef")

Part 2 (strlen "abcdef")

例3 求打印结果(sizeof/strlen 求 二维数组 各种有关元素)

例4:指针笔试题

Part 1 求程序执行的结果(int* inta[] 解引用)

Part 2 求表达式的值( 考察:指针类型决定了指针的运算 )

Part 3 求程序输出结果(32位)((int*)-(int)-ptr[-1]-*ptr2)

Part 4 求打印结果(p = a[0],打印p[0])

Part 5 求打印结果(&p[4][2] - &a[4][2])

Part 6 求打印结果(b[2][5] *(ptr1 -1) *(ptr2 - 1))

Part 7 求打印结果(char* a[] pa++ %s)

Part 8 求打印结果 (*c **cp[] ***cpp)


注:

 本笔记参考B站up鹏哥C语言的视频


复习 数组名的意义

        1.sizeof(数组名) - 表示整个数组 - 计算的是整个数组的大小;

        2.&数组名 - 表示整个数组 - 取出的是整个数组的地址。

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

例1 求打印结果(sizeof 求int a[]各种有关元素)

int main()
{
        //整型数组
	int a[] = { 1,2,3,4 };

	printf("%d\n", sizeof(a));//打印结果:4*4 = 16
	printf("%d\n", sizeof(a + 0));//打印结果:4/8
	printf("%d\n", sizeof(*a));//打印结果:4
	printf("%d\n", sizeof(a + 1));//打印结果:4/8
	printf("%d\n", sizeof(a[1]));//打印结果:4

	printf("%d\n", sizeof(&a));//打印结果:4/8
	printf("%d\n", sizeof(*&a));//打印结果:16
	printf("%d\n", sizeof(&a + 1));//打印结果:4/8
	printf("%d\n", sizeof(&a[0]));//打印结果:4/8
	printf("%d\n", sizeof(&a[0] + 1));//打印结果:4/8

	return 0;
}

一些分析(取一些代表性的):

  • printf("%d\n", sizeof(a + 0))放入sizeof内的并不是单独的a,属于上述两种情况之外的情况,所以这里的a就是首元素的地址而a + 0就是第一个元素的地址。sizeof在这里计算的是地址的大小。 (a + 1 同理)
  • printf("%d\n", sizeof(*a))此处也不是单独的a,所以a是首元素地址,解引用找到了数组内的首元素。
  • printf("%d\n", sizeof(a[1]))计算的是第二个元素的大小。

---

  • printf("%d\n", sizeof(&a))&a 取出的是整个数组的地址,但是地址的大小还是不变,sizeof计算的是一个地址的大小。
  • printf("%d\n", sizeof(*&a))存放&a需要的是一个数组指针,即 int (*p) [4] = &a 的形式,此时如果解引用&a(即*&a),找到的是原本的这个数组。sizeof还是计算一个数组的大小。(在这里 *&a == a)
  • printf("%d\n", sizeof(&a + 1))&a - 取出一个数组,&a + 1 就是跳过存储这个数组的地址,找到下一块空间的起始地址。

  • printf("%d\n", sizeof(&a[0]))a[0] - 数组的第一个元素,&a[0] - 取出数组第一个元素的地址 - sizeof计算地址大小。
  • printf("%d\n", sizeof(&a[0] + 1))寻找数组第二个元素的地址。

例2 求打印结果(sizeof/strlen 求char []各种有关元素)

Part 1(sizeof { 'a', 'b', 'c', 'd','e', 'f' })

int main()
{
	//字符数组
	char arr[] = { 'a', 'b', 'c', 'd','e', 'f' };

	printf("%d\n", sizeof(arr)); //打印结果:6
	printf("%d\n", sizeof(arr + 0)); //打印结果:4/8
	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

	return 0;
}

一些分析:

  • printf("%d\n", sizeof(arr))arr数组有6个字符元素,大小是6。arr单独放入sizeof中,计算的是数组的大小。
  • printf("%d\n", sizeof(arr + 0))arr + 0 ,则arr并没有单独放入sizeof中,arr代表首元素地址,arr + 0还是指向首元素('a')的地址。
  • printf("%d\n", sizeof(*arr))这里的arr还是数组首元素地址,解引用找到的是'a'
  • printf("%d\n", sizeof(arr[1]))找到了数组的第二个元素'b'
  • printf("%d\n", sizeof(&arr))&arr - 取出了数组的地址,*sizeof还是计算地址的大小。
  • printf("%d\n", sizeof(&arr + 1))结合上一条,&arr + 1跳过的是整个数组的地址,找到下一块空间的起始地址。

  • printf("%d\n", sizeof(&arr[0] + 1))arr[0] - 取出的是第一个元素的地址,所以&arr[0]的类型就是char*,&arr[0] + 1指向下一个元素('b')的地址。

此处如果通过调试,内存窗口可以看见:

&arr[0]

&arr[1]

元素'a'和元素'b'相差一个字节。


Part 2(strlen { 'a', 'b', 'c', 'd','e', 'f' })

int main()
{
	//字符数组
	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));//打印结果:随机值

	return 0;
}

strlen - 计算字符串的长度。

字符串的长度由终止空字符确定:字符串与 字符串开头终止空字符('\0")之间的字符数 一样长(不包括终止空字符本身)。(来源:cplusplus

一些分析:

  • printf("%d\n", strlen(arr))此处数组名arr是首元素地址,(注意:arr数组内部没有'\0')strlen无法寻找到 \0 ,字符串的长度实际上是不确定的,所以strlen函数会返回一个随机值。
  • printf("%d\n", strlen(arr + 0))arr + 0 还是首元素地址,同上。
  • printf("%d\n", strlen(*arr))注意:strlen函数的参数部分是char*,一个指针。但是这里传*arr找到的是首元素 - 字符'a',字符'a'的ASCII值是97,这里会把97当作一个地址。所以会引发异常:

 ps:61 对应的十进制就是 97。

  • printf("%d\n", strlen(arr[1]))arr[1]找到的是字符'b',同上。
  • printf("%d\n", strlen(&arr))&arr取出的是数组的地址,类型应该是 char(*)[6] ,但是传入strlen函数时,类型却变成了 char* ,所以strlen会把传来的地址认为是首元素的地址,并且从这个位置向后数字符。
  • printf("%d\n", strlen(&arr + 1))&arr + 1 找到的是数组后面的地址,无法确定strlen函数会找到哪里。
  • printf("%d\n", strlen(&arr[0] + 1))&arr[0] + 1 代表strlen从字符'b'处开始向后数,同上。

Part 3(sizeof "abcdef")

int main()
{
	char arr_1[] = "abcdef";

	printf("%d\n", sizeof(arr_1));//打印结果:7
	printf("%d\n", sizeof(arr_1 + 0));//打印结果:4/8
	printf("%d\n", sizeof(*arr_1));//打印结果:1
	printf("%d\n", sizeof(arr_1[1]));//打印结果:1
	printf("%d\n", sizeof(&arr_1));//打印结果:4/8
	printf("%d\n", sizeof(&arr_1 + 1));//打印结果:4/8
	printf("%d\n", sizeof(&arr_1[0] + 1));//打印结果:4/8

	return 0;
}

 char arr_1[] = "abcdef"   这种初始化方式,会在句尾多放一个 \0

一些分析:

  • printf("%d\n", sizeof(arr_1))arr_1内存放7个元素,sizeof计算得到7。
  • printf("%d\n", sizeof(arr_1 + 0))arr_1 + 0 是首元素地址。
  • printf("%d\n", sizeof(*arr_1))对首元素地址解引用,找到首元素'a'
  • printf("%d\n", sizeof(arr_1[1]))arr_1[1] 找到字符'b'
  • printf("%d\n", sizeof(&arr_1))&arr_1 取出这个数组的地址。(转换为数组指针的形式:char(*) [7])
  • printf("%d\n", sizeof(&arr_1 + 1))&arr_1 + 1,跳过整个数组的地址,找到数组地址后面的首地址。
  • printf("%d\n", sizeof(&arr_1[0] + 1))&arr[0] 取出第一个元素的地址,再+1,找到第二个元素的地址。

Part 4(strlen "abcdef")

int main()
{
        char arr_1[] = "abcdef";

	printf("%d\n", strlen(arr_1));//打印结果:6
	printf("%d\n", strlen(arr_1 + 0));//打印结果:6
	printf("%d\n", strlen(*arr_1));//打印结果:引发异常(调试时)
	printf("%d\n", strlen(arr_1[1]));//打印结果:引发异常(调试时)
	printf("%d\n", strlen(&arr_1));//打印结果:6
	printf("%d\n", strlen(&arr_1 + 1));//打印结果:随机值
	printf("%d\n", strlen(&arr_1[0] + 1));//打印结果:

	return 0;
}

arr_1内存放 [ a b c d e f \0 ]

一些分析:

  • printf("%d\n", strlen(arr_1))strlen遇到 \0 停止计算。
  • printf("%d\n", strlen(arr_1 + 0))arr_1 + 0 找到首元素地址,strlen从首元素开始向后数。
  • printf("%d\n", strlen(*arr_1))*arr_1 传入字符'a'的ASCII值,调试时报错。
  • printf("%d\n", strlen(arr_1[1]))arr_1[1] 找到字符'b',传入字符'b'的ASCII值,同上。
  • printf("%d\n", strlen(&arr_1))&arr_1 取出的是数组的地址,还是从首元素先后数。
  • printf("%d\n", strlen(&arr_1 + 1))&arr_1 + 1 跳过数组的地址,之后的地址是什么无法得知。
  • printf("%d\n", strlen(&arr_1[0] + 1)) :&arr_1[0] + 1 从字符'b'的位置向后数。

例3 求打印结果(sizeof/strlen 求char* []各种有关元素)

Part 1(sizeof "abcdef")

int main()
{
	char* p = "abcdef";

	printf("%d\n", sizeof(p));//打印结果:4/8
	printf("%d\n", sizeof(p + 1));//打印结果:4/8
	printf("%d\n", sizeof(*p));//打印结果:1
	printf("%d\n", sizeof(p[0]));//打印结果:1
	printf("%d\n", sizeof(&p));//打印结果:4/8
	printf("%d\n", sizeof(&p + 1));//打印结果:4/8
	printf("%d\n", sizeof(&p[0] + 1));//打印结果:4/8

	return 0;
}

一些分析:

  • printf("%d\n", sizeof(p))p是一个指针变量,大小就是4/8。
  • printf("%d\n", sizeof(p + 1))p + 1 找到的是字符'b'的地址,sizeof计算指针变量的大小。
  • printf("%d\n", sizeof(*p))*p 就是对首元素地址解引用,找到字符'a'
  • printf("%d\n", sizeof(p[0]))p[0] 等价于 *(p + 0),找到字符'b'
  • printf("%d\n", sizeof(&p))&p,取出p的地址,依旧是指针变量。
  • printf("%d\n", sizeof(&p + 1))&p + 1 跳过p的地址。

  • printf("%d\n", sizeof(&p[0] + 1))&p[0] 取出第一个元素的地址,再+1找到字符'b'的地址

Part 2 (strlen "abcdef")

int main()
{
	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]));//打印结果:引发异常(调试时)
	printf("%d\n", strlen(&p));//打印结果:随机值
	printf("%d\n", strlen(&p + 1));//打印结果:随机值
	printf("%d\n", strlen(&p[0] + 1));//打印结果:5

	return 0;
}

ps:出现的随机值完全随机,不同随机值间不互相影响。

一些分析:

  • printf("%d\n", strlen(p))p内存放字符'a'的地址,遇到 \0 停止计算。
  • printf("%d\n", strlen(p + 1))p内存放字符'b'的地址。
  • printf("%d\n", strlen(*p))*p找到的是字符'a',传入97,代码引发异常。
  • printf("%d\n", strlen(p[0]))同上。
  • printf("%d\n", strlen(&p))此处,是从p的地址开头开始向后数字符串长度,长度不可知。

  • printf("%d\n", strlen(&p + 1))跳过p的地址,从这之后开始计算,长度不可知。
  • printf("%d\n", strlen(&p[0] + 1))&p[0] 取出字符'a'的地址,+1找到字符'b'的地址。

例3 求打印结果(sizeof/strlen 求 二维数组 各种有关元素)

int main()
{
	int a[3][4] = { 0 };

	printf("%d\n", sizeof(a));//打印结果:48
	printf("%d\n", sizeof(a[0][0]));//打印结果:4
	printf("%d\n", sizeof(a[0]));//打印结果:16
	printf("%d\n", sizeof(a[0] + 1));//打印结果:4/8
	printf("%d\n", sizeof(*(a[0] + 1)));//打印结果:4
	printf("%d\n", sizeof(a + 1));//打印结果:4/8
	printf("%d\n", sizeof(*(a + 1)));//打印结果:16
	printf("%d\n", sizeof(&a[0] + 1));//打印结果:4/8
	printf("%d\n", sizeof(*(&a[0] + 1)));//打印结果:16
	printf("%d\n", sizeof(*a));//打印结果:16
	printf("%d\n", sizeof(a[3]));//打印结果:16

	return 0;
}

一些分析:

  • printf("%d\n", sizeof(a))数组a是一个3 * 4的二维数组,每个数字是4个字节,(3*4)*sizeof(int) = 48。sizeof在这里计算的是整个数组的大小。
  • printf("%d\n", sizeof(a[0][0]))a[0][0] 是第一行第一个元素,该元素是int类型。
  • printf("%d\n", sizeof(a[0]))把第一行理解为一个一维数组,a[0] 就可以理解为第一行的(一维数组的)数组名。可理解为:数组名a[0]单独放入sizeof中,a[0]表示整个第一行,sizeof计算的就是第一行的大小。
  • printf("%d\n", sizeof(a[0] + 1))a[0]不是单独放入sizeof中,a[0]就代表(第一行)首元素的地址,a[0] + 1,代表第一行的第二个元素的地址。

  • printf("%d\n", sizeof(*(a[0] + 1))) :由上一条可知,此处取出了第一行第二个元素。
  • printf("%d\n", sizeof(a + 1))此处的a没有单独放入sizeof中,也没有&,a就代表二维数组首元素的地址,即第一行的地址。所以 a + 1 就是二维数组第二行的地址。
  • printf("%d\n", sizeof(*(a + 1)))由上一条可知,*(a + 1) 得到的就是第二行。第二行有4个int类型。从代码的角度:*(a + 1) 等价于 a[1] 。
  • printf("%d\n", sizeof(&a[0] + 1))a[0] 是第一行的数组名,&a[0] 就是取出第一行的地址,&a[0] + 1 就表示第二行的地址。发现:a + 1 等价于 &a[0] + 1 。
  • printf("%d\n", sizeof(*(&a[0] + 1)))由上一条可知,*(&a[0] + 1) 是对第二行的地址解引用,得到第二行。发现:*(a + 1) 等价于 *(&a[0] + 1) 。
  • printf("%d\n", sizeof(*a))a不取地址,没有单独放入sizeof中,所以这里的a就是二维数组首元素地址,*a - 对第一行的地址解引用,找到的是第一行。发现:*a、*(a + 0)、a[0] 互相等价。
  • printf("%d\n", sizeof(a[3]))注意:这个代码没有问题。a[3] 其实是第四行的数组名(如果存在的情况下),所以即使其不存在,也可以通过类型计算大小。

表达式有两个属性:1.值属性   2.类型属性

对于 3 + 5 这个表达式

值属性 - 8

类型属性 - int

由此可知 sizeof(3 + 5) = 4。此处只要通过类型属性就可以找到结果。

故既然 a[3] 存在,尽管在二维数组内没有第四行,但都有 a[3] 的类型属性 - int [4]。此处 a[3] 不会被真实访问

知识点:sizeof()内部的表达式是不会被真正计算的。

例子:

int main()
{
	short s = 5;
	int a = 4;
	printf("%d\n", sizeof(s = a + 6));
	printf("%d\n", s);
	return 0;
}

打印结果是:

总结发现:

  1. *(a + 1) 、 a[1] 、 *(&a[0] + 1) 是互相等价的。
  2. a + 1 等价于 &a[0] + 1
  3. *a、*(a + 0)、a[0] 互相等价。

不同的写法可以有相同的效果。

例4:指针笔试题

Part 1 求程序执行的结果(int* inta[] 解引用)

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

打印结果为:2 , 5

分析:

&a + 1的类型是 int(*)[5] ,这和 int* 不匹配,需要强制类型转换。&a取出数组的地址,&a + 1 跳过整个数组,取到数组后面的地址。ptr是整型指针,减1向后移动一个整型。如图:


Part 2 求表达式的值( 考察:指针类型决定了指针的运算 )

问:如下表达式的值分别为多少?

假设p的值为0x100000。已知,结构体Test类型的变量大小是20个字节。

struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char hca[2];
	short sBa[4];
}*p;

int main()
{
	printf("%p\n", p + 0x1);
	printf("%p\n", (unsigned long)p + 0x1);
	printf("%p\n", (unsigned int*)p + 0x1);

	return 0;
}

打印结果为:

0x100014
0x100001
0x100004

分析:

指针+1,起决定作用的是指针的类型

  • *p 是一个结构体指针,这个指针可以指向结构体Test。
  • 0x1 - 十六进制的1,就是十进制的数字1+ 0x1 相当于 +1

printf("%p\n", p + 0x1);

p + 0x1 == p + 1 ,指针变量p的大小为20个字节,+1跳过了20个字节。0x100000是十六进制的表达方式,跳过20个字节就变为了0x100014,注意:这里的14是16*1 + 4。

printf("%p\n", (unsigned long)p + 0x1);

p被强制转换成一个整型类型,整型类型+1就是+1,即 100000 + 1 == 100001。所以出现0x100001。

printf("%p\n", (unsigned int*)p + 0x1);

p被强制转换成无符号整型指针类型,认为p指向的是一个整型,这时候+1就相当于地址+4,得到0x100004。


Part 3 求程序输出结果(32位)((int*)-(int)-ptr[-1]-*ptr2)

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;
}

打印结果为:

分析:

int* ptr1 = (int*)(&a + 1) :&a + 1 跳过整个数组,ptr1得到的是被 强制类型转换成 整型指针类型 后的地址。

---

int* ptr2 = (int*)((int)a + 1) :a 是首元素的地址,被强制转换为整型,整型+1就是+1,即跳过一个字节,ptr2的地址 = a的地址 + 1。故 ptr2 和 a 之间相差了一个字节。通过调试可以看到:

此时,在小端机器中存在:

换一种形式解读:

printf("%x,%x", ptr1[-1], *ptr2)

ps:%x - 以十六进制的形式打印       

       %#x - 可以在以十六进制的形式打印的同时在数字前面加上0x

ptr1[-1] : ptr1[-1] 可以转换成 *(ptr1 + (-1)) ,ptr1是整型指针,得到:0x00 00 00 04。

*ptr2 :所以对ptr2进行解引用操作,即从ptr2所指向的位置向后访问4个字节(注意:ptr2是整型指针类型的变量),所以真正通过%x打印数据,得到:0x02 00 00 00。

归纳:

特别地:

在[int* ptr2 = (int*)((int)a + 1);]中:

由于64位机器指针是8个字节,但是 (int)a + 1 在执行时,是以4个字节来执行的,而重新存储在ptr2中时,又会重新转回8个字节,可以比喻为指针发生了类似整型提升的情况。这会导致ptr2成为一个野指针,访问了一片没有权限访问的空间,此时打印结果无法显示,且调试时会发现引发异常。

如:


Part 4 求打印结果(p = a[0],打印p[0])

int main()
{
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };
	int* p;
	p = a[0];
	printf("%d", p[0]);
	return 0;
}

 打印结果为:1

分析:

int a[3][2] = { (0, 1), (2, 3), (4, 5) };  注意这个指针的初始化形式,( ,  ) 的这种形式是逗号表达式(ps:如果想把0和1放在第一行,初始化时应该是{0, 1}这种形式)

逗号表达式的结果是逗号表达式最右边的表达式的结果。如:(0, 1)的结果就是1,(2, 3)的结果就是3。

所以数组a[3][2]内存放应该是:

p = a[0]; a[0] 代表数组(第一行)首元素地址,所以p也指向了这个首元素的地址。

printf("%d", p[0]); p[0] 可以解释为 *(p + 0),所以此时访问的就是 1。


Part 5 求打印结果(&p[4][2] - &a[4][2])

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;
}

打印结果:

这是32位机器上的结果,64位机器如下:

分析:

指针和指针相减,得到的是指针之间的元素个数。

int(*p)[4]; p是一个数组指针,p能够指向的数组有4个元素,每个元素是整型。所以,对于p而言,p认为其指向的数组有4个元素

p = a; a 表示(第一行)首元素的地址,但是第一行有5个元素(a的类型可以表示为 int(*)[5],而p的类型是 int(*)[4])。p与a之间有类型差异。但是a还是可以赋给p。所以当 p + 1 时,就是跳过4个字节,再+1,再跳过4个字节。

&p[4][2]; 此时存在 p[4] 等价于 *(p + 4) 。p[4][2] 就是从 p + 4 的位置向后找到第3个元素

a[5][5]中:

&p[4][2] - &a[4][2]; 由图可知,p[4][2] 和 a[4][2] 之间相差4个元素,同时注意,这里是小地址 - 大地址,得到的是 -4。

注意:在内存中,-4 表示为:

原码:10000000 00000000 00000000 00000100

补码:11111111 11111111 11111111 11111011

反码:11111111 11111111 11111111 11111100

如果以%p打印,认为-4的补码就是个地址,补码会作为地址被直接取出(以十六进制方式打印),得到:FFFFFFFC\FFFFFFFFFFFFFFFC

ps:在执行代码时也可以看到警告:


Part 6 求打印结果(b[2][5] *(ptr1 -1) *(ptr2 - 1))

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

打印结果为:10,5

分析:

  • int* ptr1 = (int*)(&b + 1) :&b + 1 跳过二维数组b,再把这个地址强制转换成整型指针类型赋给ptr1。
  • ptr1 - 1 :是整型指针-1,指针往后移动1个字节,找到的是 10 的地址。

int* ptr2 = (int*)(*(b + 1))  :b 在这里就是(第一行)首元素的地址,这个地址+1是跳过一行,找到第二行的首元素的地址。此时 *(b + 1) 得到的是第二行的 6 的地址(ps:6的地址就是整型指针类型,这里的 (int*) 是可以不用的)。这里的 *(b + 1) 等价于 b[1] 。


Part 7 求打印结果(char* a[] pa++ %s)

int main()
{
	char* a[] = { "work", "at", "alibaba" };
	char** pa = a;
	pa++;
	printf("%s\n", *pa);
	return 0;
}

打印结果为:

分析:

复习:

char* p = "abcdef";

通过这种操作,可以把字符串首字符'a'的地址传给p。

char* a[] = { "work", "at", "alibaba" } :其中的每个元素都是char*,初始化时有3个元素(3个字符串)。所以这里把3个字符串的首字符("w" "a" "a")的地址传给了a[]。

此时a[]中:

char** pa = a :注意,数组a是char*类型的,这意味着如果要将数组a的首元素的地址存入指针内,那么就要求存放首元素地址的指针(即pa)是一个二级指针

pa++ 就相当于跳过一个元素此时存在:

所以此时pa指向的是数组a第二个元素的地址,即字符"at",此时以%s的形式打印,就是打印字符"at"


Part 8 求打印结果 (*c **cp[] ***cpp)

int main()
{
	char* c[] = { "ENTER", "NEW", "POINT", "FIRET" };
	char** cp[] = { c + 3, c + 2, c + 1, c };
	char*** cpp = cp;
	printf("%s\n", **++cpp);//打印结果为:POINT
	printf("%s\n", *-- * ++cpp + 3);//打印结果为:
	printf("%s\n", *cpp[-2] + 3);//打印结果为:
	printf("%s\n", cpp[-1][-1] + 1);//打印结果为:
	return 0;
}

打印结果为:

分析(箭头方向是存储顺序)

char* c[] :数组c的类型是char*(一级指针),数组c初始化 - 内部存放了4个字符串:

char** cp[] :类似的,数组cp的类型是char**(二级指针),数组cp初始化:

注意:这里的 c 都代表数组c的首元素的地址

char*** cpp = cp :这里cp作为char**数组的首元素地址放入cpp(三级指针)内部。

到初始化完三个指针时,调试可以看见:

------

**++cpp :首先,++cpp让指针cpp指向数组cp的下一个元素:

其次,拿到 cp[1] 的地址后,对cpp进行解引用,拿到 cp[1] 的对应元素(c + 2),而 cp[1] 对应的数组c的元素是:

此时拿到的是"POINT"的首字符"P"的地址。所以这时候打印就是从"P"的位置向后打印字符串"POINT"

------

*-- * ++cpp + 3 :首先得决定优先级,这时候++cpp的优先级是最高的,这里++cpp指向数组cp的第三个元素(注意:这里 ++cpp 在上面的代码中已经执行过一次了):

接下来* ++cpp,执行的是解引用(*)(ps:* 的优先级比 + 高),得到的就是 cp[2] 位置的元素(c + 1)

再然后是-- * ++cpp,执行前缀--,对 c + 1 执行该操作,得到的就是 c,注意此时的数组cp变为

之后执行的是*-- * ++cpp,执行又一个解引用操作,这时候是对 c 进行解引用操作,c指向的是数组c的首元素的地址(取得的是字符"E"的地址):

最后执行的是*-- * ++cpp + 3,完成 +3 的操作。这时候是对一个字符指针(数组c的类型是char*)进行 +3 的操作,是一个一个字符进行加减操作,所以:

这时候是从第二个E的地址向后打印字符串,得到"ER"

------

*cpp[-2] + 3 :首先是cpp[-2]( 其意思是*(cpp - 2) ),因为存在一次解引用操作,所以可以将*cpp[-2]写成**(cpp - 2)的形式。

综上所述,先将代码转换为 **(cpp - 2) + 3 的形式。

在之前的代码中,指针cpp已经指向了 cp[2](即 c + 1)的位置,所以这里 -2 实际上是把指针cpp指向的位置往回找了2个元素,此时cpp指向:

也就是说,对(cpp - 2)解引用一次,得到cp[0]内存放的元素 c + 3 。再对 c + 3 进行一次解引用,由于 c + 3 内存放的是 c[3] 的地址,所以再解引用一次,得到字符串"FIRST"的字符"F"的地址。

这时候指向 +3,由于数组c是char*类型,每次调用1个字节,所以存在:

此时从字符"S"的位置向后打印,得到:"ST"

------

cpp[-1][-1] + 1 :首先将 cpp[-1] 解释为 *(cpp - 1),则 cpp[-1][-1] 将被解释为 *(*(cpp - 1) -1) + 1

从最里面的cpp - 1开始寻找,在之前的代码的基础上,可以得到:

接下来对cpp指向的这个地址解引用,拿到的就是数组 cp[1] 的内容,即 c + 2。

然后是 *(cpp - 1) - 1 ,此时是对 c + 2 的内容(即对 c[2] 的地址 - 1)进行-1,得到的是 c + 1 ,此时指向的应该是 c[1] 的地址:

此时在对这个地址解引用 *(*(cpp - 1) - 1) ,得到的就是 c[1] 的内容(字符"N"的地址)。

最后是 执行+1 的操作(*(*(cpp - 1) - 1) +1) ,找到的是字符"E"的地址,此时从此处开始向后打印字符串,得到"EW"

ps:

  • 代码执行完毕后,调试可以看见:

  • 指针cpp在整串代码中变化了两次:

在此之后就没有发生过变化。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值