【逐步剖C】-第八章-指针进阶-下

news2024/11/24 14:28:15

前言:在文章【逐步剖C】-第八章-指针进阶-上与指针初阶中我们介绍了有关指针较为全面的知识,本篇文章主要从指针和数组相关试题出发,进一步巩固对指针的学习。接下来,让我们开始吧。

一、“真假”数组名

前言:这一部分设计到有关关键字sizeof与数组名的意义的知识点,若有不熟悉的朋友可以先看看下面这几篇文章中的相关介绍:
【逐步剖C】-第三章-数组
【逐步剖C】-第四章-操作符

1. 一维数组

看下面几条语句输出的结果是什么?

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

程序运行结果:
在这里插入图片描述
解释

  • sizeof(a)
    这里的a是数组名,其“单独”作为sizeof的操作数,代表了整个数组,故计算出的字节大小为16

  • sizeof(a+0)
    这里的a是数组名,但其并不“单独”作为sizeof的操作数(因为后面还有个+0),
    故此时的数组名a代表的是数组首元素的地址,是地址那么大小就是4(32位机)或8(64位机)个字节

  • sizeof(*a)
    这里的a代表的是数组首元素的地址,解引用a则得到了第一个元素,类型为整型,故大小为4个字节

  • sizeof(a+1)
    这里的a代表的是数组首元素的地址,+1则代表了数组第二个元素的地址,是地址那么大小就是4或8个字节

  • sizeof(a[1])
    a[1]就是数组的第二个元素,类型为整型,故大小为4个字节

  • sizeof(&a)
    &a是取出整个数组的地址,是地址那么大小就是4或8个字节

  • sizeof(*&a)
    &a是取出整个数组的地址,再解引用就得到了整个数组,故计算出的字节大小为16
    :不在sizeof中的*&a得到的是数组首元素的地址
    解释:这里可以理解为 * 与 & 相互抵消,抵消完后就只剩数组名“a”;
    此时在sizeof中就相当与是“单独”作为操作数,代表了整个数组;
    不在sizeof中就相当于单只有一个数组名,代表了数组首元素的地址。

  • sizeof(&a+1)
    &a是整个数组的地址,&a+1跳过整个数组指向数组后边的空间,但也是一个地址,是地址那么大小就是4或8个字节
    示意图参考:

在这里插入图片描述

  • sizeof(&a[0])
    &a[0]是取出了数组第一个元素的地址,是地址那么大小就是4或8个字节

  • sizeof(&a[0]+1)
    &a[0]是取出了数组第一个元素的地址,+1则代表了数组第二个元素的地址,是地址那么大小就是4或8个字节

小结
若数组名 “单独” 作为sizeof的操作数,则此时数组名代表的是整个数组;
解引用操作*和取地址操作&可理解为“相互抵消”;
&+数组名无论是否在sizeof中都表示的是整个数组的地址;

2. 二维数组

看下面几条语句输出的结果是什么?

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]));

程序运行结果:
在这里插入图片描述

解释

  • sizeof(a)
    数组名 “单独” 作为sizeof的操作数,a代表整个二维数组,故大小为48个字节

  • sizeof(a[0][0]))
    a[0][0]代表二维数组中第一行的第一个元素,类型为整型,故大小为4个字节

  • sizeof(a[0])
    a[0]代表二维数组中的第一行,相当于是第一行数组的数组名,即第一行数组的数组名“单独”作为sizeof的操作数,故代表整个第一行数组,大小为16个字节
    示意图参考:
    在这里插入图片描述

  • sizeof(a[0]+1)
    把a[0]视为数组名,a[0]不是“单独”作为sizeof的操作数,所以此时的a[0]表示的是第一行数组首元素的地址,故a[0]+1表示的是第一行数组的第二个元素的地址,故大小为4或8个字节
    示意图参考:
    在这里插入图片描述

  • sizeof(*(a[0]+1))
    a[0]+1表示的是第一行数组的第二个元素的地址,故解引用得到的就是第一行数组的第二个元素,类型为整型,故大小为4个字节

  • sizeof(a+1)
    a是二维数组的数组名,没有“单独”作为sizeof的操作数,故单论a而言,其代表二维数组的整个第一行的地址,+1后就代表整个第二行的地址,故大小为4或8个字节

  • sizeof(*(a+1))
    a+1表示的是整个第二行的地址,故解引用得到的就是整个第二行的数组,故大小为16字节

  • sizeof(&a[0]+1)
    &a[0]代表取出整个第一行一维数组的地址,+1后表示的是整个第二行数组的地址,故大小为4或8个字节

  • sizeof(*(&a[0]+1))
    &a[0]+1表示整个第二行数组的地址,解引用得到整个第二行数组,故大小为16个字节

  • sizeof(*a)
    a表示整个第一行的地址,解引用得到整个第一行数组,故大小为16个字节

  • sizeof(a[3])
    a[3]看似发生了越界问题,其实没有。sizeof本质获取的是操作数的类型属性,对于a[3],其相当于是数组的第四行(假设存在),类型为int[4],故大小为16个字节

小结
对于二维数组,arr[i]可视为对应第i行的一维数组的数组名;
sizeof的本质是获取操作数的值属性;

3.字符数组

看下面几条语句输出的结果是什么?
(1)

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

对sizeof:
运行结果:
在这里插入图片描述
解释

  • sizeof(arr)
    数组名arr “单独” 作为sizeof的操作数,表示的是整个数组,故大小为6个字节
  • sizeof(arr+0)
    数组名arr没有单独作为sizeof的操作数,表示的是数组首元素的地址,故大小为4或8个字节
  • sizeof(*arr)
    数组名表示的是首元素的地址,故解引用得到的是数组第一个元素,故大小为1个字节
  • sizeof(arr[1])
    arr[1]表示数组的第二个元素,故大小为1个字节
  • sizeof(&arr)
    &arr取出的是整个数组的地址,是地址就是4或8个字节
  • sizeof(&arr+1)
    &arr表示整个数组的地址,+1跳过了整个数组,但本质还是地址,故大小为4或8字节
  • sizeof(&arr[0]+1)
    &arr[0]表示的是数组第一个元素的地址,+1表示第二个元素的地址,故大小为4或8个字节

对strlen:
这里先简单说明一下strlen函数的功能:通过统计一个字符串中在结束标志'\0'前有几个字符来计算出字符串长度,如字符串"abcdef"的长度就为6。

这里没有贴上运行结果的截图,因为个别语句是对strlen函数的错误使用,后面继续说明

说明

  • strlen(arr)
    arr是数组首元素的地址,计算字符串长度,本质上是找字符串的结束标志\0,数组中并没有\0,故长度为随机值
  • strlen(arr+0)
    对于函数传参来说,arr+0同arr,故长度为随机值
  • strlen(*arr)
    *arr表示数组第一个元素,而strlen函数的参数是地址,故会此时会因参数不匹配而导致发生内存访问错误,因为此时访问的是对应元素值的地址
    调试截图如下,请看:
    在这里插入图片描述
    字符'a'的ASCII码值为97,转化为16进制就是61,这里相当于函数直接访问地址为0x00000061的内存空间,属于非法内存访问。
  • strlen(arr[1])
    arr[1]表示数组第二个元素,同上属于非法访问内存,程序出错。
  • strlen(&arr)
    &arr虽代表整个数组地址,但地址值与首元素是相同的(文章 【逐步剖C】-第八章-指针进阶-上中有详细讲解,感兴趣的朋友可以看看),故strlen同样从首元素的地址开始往后找'\0',故语句执行结果为随机值。
  • strlen(&arr+1)
    &arr+1,代表跳过了一整个数组即数组最后一个元素往后一个内存单元的地址,strlen从该地址开始往后找'\0',故语句执行结果为随机值
    示意图参考:
    在这里插入图片描述
  • strlen(&arr[0]+1)
    &arr[0]+1表示数组第个二元素的地址,从此往后找'\0',故语句执行结果为随机值
    示意图参考:
    在这里插入图片描述

(2)

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

(2)与(1)相比,数组中多存储了'\0',示意图参考:
在这里插入图片描述

对sizeof

  • sizeof(arr)
    数组名arr“单独”作为操作数,代表了整个数组,故大小为7个字节
    往后的语句执行结果同(1),这里不再赘述啦。

对strlen

  • strlen(arr)
    数组首元素的位置开始往后找'\0''\0'前共有6个字符,故字符串长度为6个字节
  • strlen(arr+0)
    对于函数传参来说,arr+0同arr,故长度为6个字节
  • strlen(*arr)
    *arr表示数组第一个元素,而strlen函数的参数是地址,故会此时会因参数不匹配而导致发生内存访问错误,因为此时访问的是对应元素值的地址
  • strlen(arr[1])
    arr[1]表示数组第二个元素,同上属于非法访问内存,程序出错。
  • strlen(&arr)
    &arr虽代表整个数组地址,但地址值与首元素是相同的,故strlen同样从首元素的地址开始往后找'\0',故长度为6个字节。
  • strlen(&arr+1)
    &arr+1,代表跳过了一整个数组即数组最后一个元素往后一个内存单元的地址,strlen从该地址开始往后找'\0',故语句执行结果为随机值
    参考示意图:
    在这里插入图片描述
  • strlen(&arr[0]+1)
    &arr[0]+1代表数组第二个元素的地址,即从数组第二个元素开始往后找'\0',故长度为5个字节

(3)

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

对sizeof
程序运行结果:
在这里插入图片描述

  • sizeof(p)
    p本质上存储的是字符串首字符的地址,故大小为4或8个字节
  • sizeof(p+1)
    p+1表示字符串第二个字符的地址,故大小为4或8个字节
  • sizeof(*p)
    *p表示字符串的首字符,故大小为1个字节
  • sizeof(p[0])
    p[0]表示字符串的首字符,故大小为1个字节
  • sizeof(&p)
    &p表示字符串首字符的地址的地址,是地址大小就为4或8个字节
  • sizeof(&p+1)
    &p+1表示字符串首字符的地址的地址往后一个字符大小空间的地址,是地址大小就为4或8个字节
    示意图参考:

在这里插入图片描述

  • sizeof(&p[0]+1)
    表示字符串中第二个字符的地址,故大小为4或8个字节
    对strlen
  • strlen(p)
    字符串首字符的位置开始往后找'\0''\0'之前有6个字符,故长度为6个字节
  • strlen(p+1)
    字符串第二个字符的位置开始往后找'\0',故长度为5个字节
    往后语句执行的结果同(2),这里不再赘述啦.

4. 总结

数组名的意义

(1)sizeof(数组名),即数组名单独作为sizeof的操作数,这里的数组名表示整个数组,计算的是整个数组的大小。
(2)&数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
(3)除此之外所有的数组名都表示首元素的地址。

二、指针笔试题

数组名的意义介绍完了,接下来我们来和指针比划比划。
先声明一下这里需要用到的知识点及相关文章:
(1)所谓取地址其实取出的是第一个字节的地址(较小的地址);指针类型的意义;同类型指针相减;指针加减整数——【逐步剖C】-第五章-指针初阶
(2)数据在内存中的存储方式:大小端字节序 ——【逐步剖C】-第七章-数据的存储
(3)数组指针 ——【逐步剖C】-第八章-指针进阶-上
1. 程序的结果是什么

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

运行结果:
在这里插入图片描述
解释
&a+1表示的是跳过整个数组后即数组最后一个元素后面的一块内存空间的地址,此时的地址类型为数组指针,即int(*)[5],故在强制转化为整型后才用指针ptr指向那块空间,因为ptr为整型指针,所以在执行 -1后指针往前走了一个整型的大小 ,即指向了数组中的元素5

数组名a表示首元素的地址,故a+1表示第二个元素的地址,执行解引用操作就得到第二个元素。
参考示意图:
在这里插入图片描述
2. 假设p的值为0x100000,已知结构体Test类型的变量大小是20个字节,如下表达式的值分别为多少

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

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

运行结果:
在这里插入图片描述
解释:这里主要考察指针类型在加减整数上的意义。

  • 语句printf("%p\n", p + 0x1);
    这里的p为结构体指针,由已知,结构体的大小为20个字节,故在+0x1(其实也就是+1)后,指针往后走了20个字节,20转化为16进制就是14,故最终结果为00100014
  • 语句printf("%p\n", (unsigned long)p + 0x1);
    把p强制转换为了无符号长整型,此时+1就是和整数一样普通的+1,故最终结果为00100001
  • 语句printf("%p\n", (unsigned int*)p + 0x1);
    把p强制转换为了无符号整型指针,执行+1后,指针往后走了4个字节,故最终结果为00100004

3. 下面程序输出结果是什么?

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即最后一个元素后面一块内存空间的地址,此时的地址类型为数组指针,即int(*)[4],故在强制转化为整型后才用指针ptr指向那块空间。在打印语句中的ptr1[-1]其实就等价于*(ptr-1),又因为ptr为整型指针,故在-1后往前走一个整型的大小指向了数组中元素4的位置,故最终输出的结果为4
    示意图参考:

在这里插入图片描述

  • 对语句int *ptr2 = (int *)((int)a + 1);
    我们知道a表示的是数组首元素的地址,但更准确来说是首元素的第一个字节的地址(整型有四个字节),这一点我们可以通过调试看到:
    在这里插入图片描述
    这个是整个数组的地址即内存情况,可以看到一共是16个字节,行与行之间差了4个字节,接下来我们一个字节一个字节地看
    在这里插入图片描述
    可以看到,红色部分就是a的地址
    接着,强制把其转换为整型然后+1,本质上就是让其往后加了一个字节,在上图就是从0x00cff9a8变为了0x00cff9a9,如上图蓝色部分所示。强制转换为整型后,其实就是相应地址值对应的十进制数,+1后再通过(int*)把其转换回了地址值,然后把这个地址存在了整型指针ptr2中。这里又涉及到了指针类型的另一个意义:指针类型决定了其在解引用时能访问的字节数。那么对于整型指针ptr2而言,其在解引用时就会以整型的视角从其地址开始往后访问4个字节,如下图:
    在这里插入图片描述
    最后,因为机器是以小端字节序的方式进行数据的存储,故ptr2指针中的内容实际上是这样的:
高位---------------------低位
02 00 00 00
高地址-------------------低地址

最后以十六进制输出,那么刚好就是2000000。

4. 下面程序输出结果是什么?

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

运行结果:
在这里插入图片描述

解释
初始化列表中的其实是逗号表达式,即语句int a[3][2] = { (0, 1), (2, 3), (4, 5) };等价为int a[3][2] = { 1, 3, 5 };接着,a[0]理解为二维数组中第一行数组的数组名代表了第一行数组中首元素的地址,该地址存在了指针变量p中,最后的p[0]就相当于*(p+0),即得到的是第一行数组的第一个元素,故最后输出结果为1。
示意图参考:
在这里插入图片描述

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

运行结果:
在这里插入图片描述
解释
解这道题的关键在于对数组指针的理解。

  • 语句int(*p)[4];表明p是一个数组指针,其指向了一个有四个整型元素的的数组,此时p若执行+1操作,就会跳过4个整型元素的大小;
  • 语句p = a;表明p指向二维数组中的第一行数组;
  • 最后,需要以地址与整型的形式输出&p[4][2] - &a[4][2]的结果。那么这里我们可以先算出整数的结果,然后根据补码转换为16进制数即可(PS:若是负数,地址打印直接就是补码,不需要再转换为原码)
    这里先放上一张示意图,可能更便于理解一些:
    在这里插入图片描述

对于p[4][2],其本质上等价于*(*(p+4)+2),由于p指向二维数组中的第一行数组,所以p从第一行数组的第一个元素的位置开始向后走(第一行数组的地址与第一行数组的首元素的地址相同),最内层括号中的p+4表示p走过了4个有4个整型元素的数组也就是16个字节的大小;*(p+4)后,指针的类型 “相当于” 从int(*)[4]型变为了int*型,故再+2就是在原来的基础上再往后走了两个整型的大小,最后解引用得到对应位置上的元素;a[4][2]指的就是二维数组第四行的第二个元素
分别对这两个元素进行取地址后作差,作差的结果若以整数的绝对值来表示,表示的就是两个地址之间的元素个数,如图中,&p[4][2]&a[4][2]之间就有4个元素,但由于a[4][2]的地址大于p[4][2]的地址,故&p[4][2] - &a[4][2]以整数输出的最终结果就为 -4
接下来将-4转换为16进制数:

-4的原码为:
1000 0000 0000 0000 0000 0000 0000 0100
反码为:
1111 1111 1111 1111 1111 1111 1111 1011
补码为:
1111 1111 1111 1111 1111 1111 1111 1100
转换为16进制就是:
ff ff ff ff fc

6. 下面程序输出结果是什么?

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

运行结果:
在这里插入图片描述
解释

  • 对于语句int *ptr1 = (int *)(&aa + 1);
    &aa表示取出整个二维数组的地址,+1后跳过整个二维数组即指向二维数组最后一个元素后面的一块内存空间,将其强制转换为int*后存在指针ptr1中,故ptr1 - 1就让ptr1指向了元素10的位置,故解引用的结果就为10。
  • 对于语句int *ptr2 = (int *)(*(aa + 1));
    aa表示的是二维数组中整个第一行数组的地址,+1后跳过整个第一行到第二行,即指向了元素6的位置,故ptr2 - 1就让ptr2指向了元素5的位置,故解引用的结果就为5。
    示意图参考:
    在这里插入图片描述

7. 下面程序输出结果是什么?

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

输出结果:
在这里插入图片描述
解释
示意图参考:
在这里插入图片描述

  • a是一个字符指针数组,每个元素都是不同字符串首字符的地址;
  • pa是一个二级指针,其存放的是字符指针数组中首元素的地址,故pa++就指向了字符指针数组中第二个元素的地址;故解引用得到第二个字符串的首字符的地址,故最后输出结果为at。

8. 下面程序输出结果是什么

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

输出结果:
在这里插入图片描述
解释

  • 三条初始化语句
    一级指针数组c中有4个元素,每个元素都是对应字符串首字符的地址;
    二级指针数组cp中有4个元素,每个元素都是对应一级指针数组中元素的地址;
    三级指针cpp中存储的是二级指针数组中首元素的地址。

示意图参考:
在这里插入图片描述

  • 语句printf("%s\n", **++cpp);
    ++cpp后cpp指向二级字符指针数组的第二个元素,解引用得到该元素,再解引用就得到对应一级字符指针数组中的元素,也就是一级字符指针数组中的第三个元素(c+2),故输出结果为POINT
    示意图参考:
    在这里插入图片描述

  • 语句printf("%s\n", *--*++cpp+3);
    在上一条语句的基础上,++cpp后cpp指向二级字符指针数组的第三个元素,解引用得到该元素后,执行--改变了二级字符指针数组中的第三个元素(c+1),使其等于了该数组中的第四个元素(c);此时解引用就得到了对应一级字符指针数组中的字符串"ENTER"首字符的地址,+3后得到字符'E'的地址,故最后输出结果为ER
    示意图参考:
    在这里插入图片描述

  • 语句printf("%s\n", *cpp[-2]+3);
    由前面语句,cpp指向二级字符指针数组的第三个元素,又因为cpp[-2]等价于*(cpp-2),即解引用得到了二级字符指针数组的第一个元素(c+3),再解引用得到了一级字符指针数组中的字符串"FIRST"首字符的地址,+3后得到字符'S'的地址,故最后输出结果为ST

  • 语句printf("%s\n", cpp[-1][-1]+1);
    cpp[-1][-1]就等价于*(*(cpp - 1) - 1)*(cpp - 1)得到的是二级字符指针数组的第二个元素(c+1),然后-1得到该数组的原来第三个元素(c+2-1 = c+1),(注:虽然c+1已经不在二级指针数组cp中,但仍能通过c+1本身直接找到对应字符串,因为本质上和c有关) 解引用得到一级字符指针数组中的字符串"NEW"首字符的地址,+1后得到字符'E'的地址,故最后输出结果为EW。

本章完。

看完觉得有觉得帮助的话不妨点赞收藏鼓励一下,有疑问或看不懂的地方或有可优化的部分还恳请朋友们留个评论,多多指点,谢谢朋友们!🌹🌹🌹

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/373903.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【每日一题】集合汇总 集合面试题

集合前言&#xff1a;图片一、集合分类1、实现 Collection 接口2、实现 Map 接口二、实现类定义1、ArrayList&#xff08;非线程安全&#xff09;2、LinkedList&#xff08;非线程安全&#xff09;3、HashSet&#xff08;非线程安全&#xff09;4、TreeSet&#xff08;非线程安…

Linux学习(8.6)文件与目录的默认权限与隐藏权限

目录 文件与目录的默认权限与隐藏权限 文件的默认权限&#xff1a;umask chattr (配置文件隐藏属性) lsattr (显示文件隐藏属性) 文件特殊权限&#xff1a; SUID, SGID, SBIT 观察文件类型&#xff1a;file 以下内容转载自鸟哥的Linux私房菜 文件与目录的默认权限与隐藏权…

比特数据结构与算法(第四章_中_续①)堆排序(详解)

本篇讲讲八大排序之一的&#xff1a;堆排序 概念复习&#xff1a;比特数据结构与算法&#xff08;第四章_上&#xff09;树和二叉树和堆的概念及结构_GR C的博客-CSDN博客一、堆排序的概念堆排序&#xff08;Heapsort&#xff09;&#xff1a;利用堆积树&#xff08;堆&#xf…

【博学谷学习记录】超强总结,用心分享 | 架构师 Tomcat源码学习总结

文章目录TomcatTomcat功能需求分析Tomcat两个非常重要的功能&#xff08;身份&#xff09;Tomcat的架构&#xff08;设计实现&#xff09;连接器的设计连接器架构分析核心功能ProtocolHandler 组件1.EndPoint组件EndPoint类结构图2.Processor组件Processor类结构图3.Adapter组件…

3.2 网站图的爬取路径

深度优先与广度优先方法都是遍历树的一种方法&#xff0c;但是网站的各个网页 之间的关系未必是树的结构&#xff0c;它们可能组成一个复杂的图形结构&#xff0c;即有回路。如果在前面的网站中每个网页都加一条Home的语句&#xff0c;让每个网页都能回到主界面&#xff0c;那么…

windows服务器实用(4)——使用IIS部署网站

windows服务器实用——IIS部署网站 如果把windows服务器作为web服务器使用&#xff0c;那么在这个服务器上部署网站是必须要做的事。在windows服务器上&#xff0c;我们一般使用IIS部署。 假设此时前端给你一个已经完成的网站让你部署在服务器上&#xff0c;别人可以在浏览器…

【Linux】-- 基于阻塞队列的生产者消费者模型

目录 前言 总结&#xff1a; 第一个问题的解决 基于BlockingQueue的生产者消费者模型 第二个问题的解决 wait的唤醒漏洞 深度理解生产者消费者模型 代码体现 锁的设计 总结&#xff1a; 前言 在多线程的条件变量遗留到此的问题。 #问&#xff1a;条件满足时&#xff0…

linux 防火墙管理-firewalld

什么是Firewalld 当前很多linux系统中都默认使用 firewalld&#xff08;Dynamic Firewall Manager of Linux systems&#xff0c;Linux系统的动态防火墙管理器&#xff09;服务作为防火墙配置管理工具。 “firewalld”是firewall daemon。它提供了一个动态管理的防火墙&#x…

Java知识复习(五)JVM虚拟机

1、虚拟机的自动内存管理和C/C的区别 C/C开发程序时需要为每一个new操作去写对应的delete/free操作&#xff0c;不容易出现内存泄漏和溢出问题。而Java程序将内存控制权交给了Java虚拟机 2、JVM的运行机制 1、Java程序的具体运行过程如下&#xff1a; Java源文件被编译器编…

c盘爆满--如何清理电脑C盘

问题 c盘饱满很多天了&#xff0c;今天终于忍无可忍&#xff0c;开始展开对c盘的处理 c盘的基本处理有两步&#xff0c; 第一步&#xff0c;电脑系统清理 1,c盘右键属性&#xff0c;有个磁盘清理&#xff0c;好像是系统更新的一些缓存资源&#xff0c;可以直接清理 当然这只…

Hadoop MapReduce

目录1.1 MapReduce介绍1.2 MapReduce优缺点MapReduce实例进程阶段组成1.3 Hadoop MapReduce官方示例案例&#xff1a;评估圆周率π&#xff08;PI&#xff09;的值案例&#xff1a;wordcount单词词频统计1.4 Map阶段执行流程1.5 Reduce阶段执行流程1.6 Shuffle机制1.1 MapReduc…

BigScience bloom模型

简介项目叫 BigScience,模型叫 BLOOM,BLOOM 的英文全名代表着大科学、大型、开放科学、开源的多语言语言模型。拥有 1760 亿个参数的模型.BLOOM 是去年由 1000 多名志愿研究人员,学者 在一个名为“大科学 BigScience”的项目中创建的.BLOOM 和今天其他可用大型语言模型存在的一…

信号的FFT变换与加窗

1. fft 傅里叶变换 1.1 傅里叶变换的本质 数学上有一种公式叫做 泰勒展开&#xff1a; 泰勒公式&#xff1a; 其表达的思想&#xff0c;是任意一函数可以有多个指数函数构成 当指数函数的个数趋近于无穷多个&#xff0c;那么组合出来的函数将会逼近原函数&#xff1b; …

Pandas数据查询

Pandas数据查询 Pandas查询数据的几种方法 df.loc方法&#xff0c;根据行、列的标签值查询 df.iloc方法&#xff0c;根据行、列的数字位置查询 df.where方法 df.query方法 .loc既能查询&#xff0c;又能覆盖写入&#xff0c;强烈推荐&#xff01; Pandas使用df.loc查询数据…

深度学习基础(二)-学习是怎么个回事

深度学习基础(一) 引入了一个 helloworld&#xff0c;提出了神经网络的简单关系&#xff0c;也就是一个基础公式 a(L) Sigmoid( a(L-1)*W(L) b(L)) a(L): 第L层神经元被激活之后 进行Sigmoid函数收敛 得到的值 b(L): 第L层神经元被激活阈值 W(L): 第L层神经元 与 第L-1层…

Android安卓中jni封装代码打包为aar

前文【Android安卓中jni与Java之间传递复杂的自定义数据结构】已经介绍jni编译c++代码且已经成功封装成java,但是c++是以源代码形式继承在app中,本文介绍如何将前述jni c++代码以隐藏源代码封装成aar的形式。 1、aar打包 1.1、新建module 按照流程 File -> New Module …

学习周报2.26

文章目录前言文献阅读摘要方法结果深度学习Encoder-Decoder&#xff08;编码-解码&#xff09;信息丢失的问题Attention机制总结前言 This week,I read an article about daily streamflow prediction.This study shows the results of an in-depth comparison between two di…

Oracle-RAC集群主机重启问题分析

问题背景: 在对一套两节点Oracle RAC19.18集群进行部署时&#xff0c;出现启动数据库实例就会出现主机出现重启的情况&#xff0c;检查发现主机重启是由于节点集群被驱逐导致​。 问题: 两节点Oracle RAC19.18集群,启动数据库实例会导致主机出现重启。 问题分析: 主机多次出现…

2023年第八周总周结 | 开学倒数第一周

为什么要做周总结&#xff1f; 1.避免跳相似的坑 2.客观了解上周学习进度并反思&#xff0c;制定可完成的下周规划 一、上周存在问题 发现自己反复犯同样问题&#xff0c;不想反思就不会意识到。总想以面带点的学习&#xff0c;实际上却在原地踏步。问题导向使用ChatGPT&#…

目标检测:DETR详解

1. 概述 DETR: End-to-End Object Detection with Transformers, DETR 是 Facebook 团队于 2020 年提出的基于 Transformer 的端到端目标检测,是Transformer在目标检测的开山之作 – DEtection TRansformer。 相比于传统的Faster-rcnn,yolo系列,DETR有以下几个优点:1).无需…