详解C语言—进阶指针(三)

目录

1、指针和数组笔试题解析

数组名的理解 :

 一维数组: 

 字符数组:

字符串数组 :

 指针:

二维数组: 

2、指针笔试题 

例一: 

扫描二维码关注公众号,回复: 17054302 查看本文章

例二:

例三:

例四:

 例五:

 例七:


1、指针和数组笔试题解析

数组名的理解 :

数组名是数组首元素的地址,

但是有2个例外:
1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节
2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址 。

在这之前我们复习一下字符串函数strlen和操作符sizeof怎么使用:sizeof与strlen辨析

 一维数组: 

创建一个含有四个整型元素的数组。

#include <stdio.h>
#include <string.h>
int main()
{
    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));
    return 0;
}

我们来依次解释每句: 

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

 a是数组名,sizeof(a)计算整个数组的大小,int类型大小是四个字节,所以结果为4*4=16个字节。

printf("%d\n",sizeof(a+0));

   数组名a是数组首元素的地址,a+0还是首元素地址,地址大小为4(32位)或8(64位)字节。

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

   数组名a是数组首元素的地址,*a就是首元素,大小是4个字节。

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

   数组名a是数组首元素的地址,a+1是第二个元素的地址,地址大小为4(32位)或8(64位)字节。

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

  a[1] 是第二个元素,大小为4个字节。

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

  &a 是数组的地址,数组的地址也是地址,大小是4或8个字节。

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

  *&a等于a,计算整个数组大小,为16个字节 

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

  &a+1相对于&a是跳过了整个数组,但是即使跳过整个数组, &a+1依然是地址,地址就是4或8个字节。

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

 &a[0]是首元素地址,为4或8个字节。

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

 &a[0]+1是第二个元素的地址,为4或8个字节。

 字符数组:

#include <stdio.h>
#include <string.h>
int main()
{
    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));
    return 0;
}

这么多例子不用慌,我们来依次解释每句 :

char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr+0));

   arr + 0与arr不同:

  •   数组名单独放在sizeof内部,这里的arr表示整个数组,计算的是整个数组的大小,单位是字节,总共6个字节。
  •   arr表示数组首元素的地址,arr+0还是数组首元素的地址,是地址就是4/8个字节 。
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
  • arr表示数组首元素的地址,*arr就是首元素,大小1个字节 。
  • arr[1]就是第二个元素,大小是1个字节。
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr+1));
printf("%d\n", sizeof(&arr[0]+1));
  • &arr是数组的地址,但是数组的地址也是地址,是地址就是4/8 。
  • &arr + 1是跳过整个数组后的地址,是地址就是4/8个字节。
  • &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));
  •  数组名arr是一个指向数组首元素的指针,因为字符数组arr中没有\0,所以在求字符串长度的时候,会一直往后找,产生的结果就是随机值。
  • arr + 0是首元素的地址,和第一个一样,也是随机值。
  • &arr是数组的地址,数组的地址和数组首元素的地址,值是一样的,与strlen(arr)和strlen(arr+0)相同,传递给strlen函数后,依然是从数组的第一个元素的位置开始往后,统计产生的结果就是随机值
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
  •      arr是数组首元素的地址,*arr就是数组首元素,也就是'a'-97。strlen函数参数的部分需要传一个地址,当我们传递的是'a'时,'a'的ASCII码值是97,那就是将97作为地址传参。                     这时strlen就会从97这个地址开始统计字符串长度,这就构成非法访问内存了,因为这块空间不属于当前程序,我们未申请这块空间,我们无法访问。
  • arr[1]='b',将'b'的ASCII值98作为地址传参,与上述传递*arr的情况相同。
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));
  •  &arr + 1是跳过整个数组后的地址,是地址就是4/8个字节。
  • 第二个元素的地址,是4/8个字节。

字符串数组 :

int main()
{
	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));
	return 0;
}
  • arr与arr + 0相等,都指向字符串首地址,strlen从首地址开始向后查找到’ \0 ‘结束计算,计算出长度为6。
  • arr指向字符串首元素a的地址,所以*arr=a,将a的ASCII值97作为地址传递给strlen,由于我们并未地址97处开辟空间,这样会造成非法访问内存。
  •  同理,arr[1]为第二个元素b,将b的ASCII值97作为地址传递给strlen,同样造成非法访问内存。
  •  &arr:&数组名等于整个数组,结果为6。
  • &arr + 1传递跳过整个数组后的地址并向后查,我们不知道’ \0 ‘会出现在哪,所以结果为随机值。
  • &arr[0] + 1从b的地址开始向后计算长度。

想必你应该了解的差不多了,下面的我就不一一展开介绍了。 

int main()
{
	printf("%d\n", sizeof(arr + 0));    //arr + 0是首元素的地址4/8个字节
	printf("%d\n", sizeof(*arr));       //*arr其实就是首元素,1个字节
	//*arr == *(arr+0) == arr[0]
	printf("%d\n", sizeof(arr[1]));     //arr[1]是第二个元素,1个字节
	printf("%d\n", sizeof(&arr));       //&arr是数组的地址,是地址就是4/8个字节
	printf("%d\n", sizeof(&arr + 1));   //&arr + 1是跳过一个数组的地址,4/8
	printf("%d\n", sizeof(&arr[0] + 1));//&arr[0] + 1是第二个元素的地址 4/8
	return 0;
}

 指针:

int main()
{
	char* p = "abcdef";//将字符串中a的地址存放到指针p
	  //下标: 012345
	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));

	return 0;
}
  • 指针p指向字符串的首地址,从首地址向后计算遇到 \0 停止,结果为6.
  • p+1指向字符串第二个元素b的地址,从b开始向后计算遇到 \0 停止,结果为5.
  • *p等于p[0],它们都指向字符串首元素a的地址,所以*arr=a,将a的ASCII值97作为地址传递给strlen,由于我们并未地址97处开辟空间,这样会造成非法访问内存。
  • p指向a的地址,所以&p指向a地址中第一个字节。                                                                          比如a地址为,则&p指向第一个字节0x
  • 同理&p + 1指向a地址的第二个字节00 ,但我们并不知道a的地址中何时有使strlen停止的\0,    所以strlen( &p和&p+1)的结果为随机值。

int main()
{
    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));

	return 0;
}
  •  p是一个指针变量,大小是4/8字节。
  • p+1是'b'的地址,是地址大小就是4/8个字节。
  • *p 就是'a',就是1个字节。
  • p[0] == *(p+0) == *p  ,为1个字节
  • &p 为 char**类型,  计算的是指向指针 p 的指针的大小,大小为4/8个字节。
  • 同理&p+1也一样。大小为4/8个字节。
  •  &p[0] + 1得到是'b'的地址 ,大小为4/8个字节。

二维数组: 

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

	printf("%d\n", sizeof(a));//3*4*4 = 48
	printf("%d\n", sizeof(a[0][0]));//4

	printf("%d\n", sizeof(a[0]));//a[0]是第一行这个一维数组的数组名
	//数组名算是单独放在sizeof内部了,计算的是整个数组的大小,大小是16个字节

	printf("%d\n", sizeof(a[0] + 1));//?
	//a[0]作为第一行的数组名,没有单独放在sizeo内部,没有&
	//a[0]表示数组首元素的地址,也就是a[0][0]的地址
	//所以a[0]+1是第一行第二个元素的地址,是地址就是4/8个字节
	//
	printf("%d\n", sizeof(*(a[0] + 1)));//4
	//计算的是就是第一行第2个元素的大小

	printf("%d\n", sizeof(a + 1));//4 / 8
	//a是数组首元素的地址,是第一行的地址 int(*)[4]
	//a+1 就是第二行的地址

	printf("%d\n", sizeof(*(a + 1)));//16
	//*(a+1) --> a[1] -> sizeof(*(a+1))->sizeof(a[1]) 计算的是第二行的大小
	//a+1 --> 是第二行的地址,int(*)[4]
	//*(a+1) 访问的第二行的数组

	printf("%d\n", sizeof(&a[0] + 1));//4/8
	//&a[0]是第一行的地址 int(*)[4]
	//&a[0]+1 是第二行的地址 int(*)[4]

	printf("%d\n", sizeof(*(&a[0] + 1)));//16 计算的是第二行的大小

	printf("%d\n", sizeof(*a));//计算的是第一行的大小-16
	//a是数组首元素的地址,就是第一行的地址
	//*a 就是第一行
	//*a --> *(a+0) --> a[0]

	printf("%d\n", sizeof(a[3]));//16
	//a[3]--> int [4]  // sizeof只看类型,不进行计算
	//
	return 0;
}

2、指针笔试题 

例一: 

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

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

假设p 的值为0x100000。 如下表表达式的值分别为多少?
已知,结构体Test类型的变量大小是20个字节

  •  p + 0x1 使指针 p 增加了 0x1,即一个地址单元(根据 p 的类型)。然后,它使用 %p 格式符打印增加后的指针的值。这将打印出 p 原始地址加上一个地址单元的地址,因为 p 是一个指向 struct Test 类型的指针,所以它会移动一个 struct Test 的大小,即在p的地址加上20个字节,因为地址通常是十六进制形式,20的十六进制形式为14 ,                                              所以结果为0x100000+14=0x100014
  • (unsigned long)p + 0x1 将指针 p 转换为 unsigned long 类型,然后增加 0x1。这里的 0x1 表示十六进制数,等于十进制的 1。在这种情况下,整数类型加1确实会将值增加1,不会根据类型不同导致加1的结果不同。所以,结果是 0x100000 + 1 = 0x100001。这个结果表示 p 的地址增加了一个地址单元的大小,因此变为 0x100001

  • (unsigned int*)p + 0x1 首先将指针 p 转换为 unsigned int* 类型,然后增加 0x1。这将导致指针增加一个unsigned int* 的大小(通常是4个字节),结果为0x100004.

输出结果如下: 

 

例二:

int main() 
{
	int a = 7;
	short s = 4; 
	printf("%d\n", sizeof(s = a + 2));
	printf("%d\n", s);
	return 0;
}
  • sizeof只判断类型,s=a+2将两个整型a+2的结果赋值给short类型s,s依然是short类型没有改变,所以输出结果为2。 
  • 程序执行流程:编译    +  链接 --> 可执行程序--> 运行---> 结果
  • 内部表达式s = a + 2在运行时才计算,而sizeof在编译期间就已经知道结果,所以s = a + 2不会进行计算,s没有赋值为a+2. 所以第二个输出语句结果为s的初始值4.

例三:

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跳过数组大小个字节,指向了数组 a 的内存位置之后的第一个字节,同时被强制转换类型为int*类型。
  • *(ptr - 1)将ptr指向的地址向前移动一个字节,使其指向数组最后一个元素5的地址,同时解引用得到整型5
  • a+1指向数组第二个元素的地址,*(a + 1)解引用得到整型2.

例四:

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

	return 0;
}

首先看 int* ptr1 = (int*)(&a + 1);

  • &a+1 取地址数组名a+1跳过数组大小个字节,指向了数组 a 的内存位置之后的第一个字节,同时被强制转换类型为int*类型。
  • ptr1[-1]等于*(ptr-1),将ptr指向的地址向前移动一个字节,指向数组a中第四个元素4的地址,并解引用得到整型4,输出结果为4.

再来看int* ptr2 = (int*)((int)a + 1);

  • (int)a 将数组 a 的起始地址转换为整数值,这个整数值代表数组的内存位置。

  • + 1 操作将地址值增加了1,这意味着它会指向地址a的下一个字节。

  1. 数组a中储存的1,2,3,4各自是一个整形,各占四个字节,我们机器是小端存储,在内存中,倒着存,因为1的十六进制是0x 00 00 00 01                      
  2. 小端存储低字节放低地址,低到高地址 对应01 00 00 00              小端字节存储后,输出需倒序还原回
  3. 所以(int)a + 1指向01后面的第一个00.

  • (int*)((int)a + 1) 强制类型转换为int*类型,使a变成存放四个整形元素的指针数组。

  •  红框圈出部分即为指针数组a存放的四个字节的地址

 *ptr2解引用访问a中的数据,输出前将其还原为,以十六进制打印时2前面的0不打印,输出结果为2000000 

 

 例五:

int main()
{
	int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	int* ptr1 = (int*)(&aa + 1);
	int* ptr2 = (int*)(*(aa + 1));
	printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
	return 0;
}
  •  &aa+1跳过数组大小个字节,指向了数组 a 的内存位置之后的第一个字节,同时被强制转换类型为int*类型。
  • *(ptr1 - 1)将ptr指向的地址向前移动一个字节,使其指向数组最后一个元素“ 10 ”的地址,同时解引用得到整型10。
  • (int*)(*(aa + 1))表示将第二个一维数组的第一个元素的地址转换为int* 类型的指针。
  • 在这里,(int*)将* (aa + 1)的结果转换为int* 类型的指针。
  • 在没有类型转换的情况下,* (aa + 1)的结果是一个int类型的值,即第二个一维数组的第一个素的值,即6。
  •     通过进行类型转换(int*)(*(aa + 1)),我们将这个int类型的值的地址转换为了int* 类型的指针。这样,指针ptr2就指向了数组aa中第二个一维数组的第一个元素,*(ptr2 - 1)得到第二个一维数组的第一个元素前一个元素,也就是第一个一维数组的第五个元素即 “ 5 ”. 

 例七:

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;
}
printf("%s\n", **++cpp); 
  • 1、这一行先对 cpp 进行递增操作,使其指向 cp 中的下一个元素也就是第二个元素。
  • ++cpp指向cp第二个元素相当于cp+1,
  • 然后,进行两次解引用操作,
  • 第一次解引用*++cpp变成c+2,即访问cp第二个元素c+2,c+2指向数组c的第三个元素
  • 第二次解引用*c+2变成"POINT",即访问数组c的第三个元素"POINT"
  • 因此,这一行打印输出的是 "POINT"。
printf("%s\n", *-- * ++cpp + 3);
  •     2、优先级++ -- > * > +,计算顺序*--*++cpp按照右结合性,最后计算+3 首先这一行先对cpp进行递增操作,在上一行代码中cpp指向cp第二个元素c+2,
  • 所以++cpp指向cp第三个元素c+1,
  • 然后解引用++cpp等于访问c+1,此时*++cpp变成c+1,
  • 接着进行递减操作,c+1变成c,解引用c访问"ENTER",*c储存字符串"ENTER"的首元素地址,也就是第一个字符'E'的地址
  • 最后*c+3跳过三个字符变成'E'的地址,输出从E开始到\0的字符串"ER" 
printf("%s\n", *cpp[-2] + 3);	
  • 3、*cpp[-2]等于**(cpp-2) + 3,    
  • 在第二段代码中++cpp指向cp第三个元素c+1,所以cpp-2指向c+3
  • **(cpp-2)+3 = *(c+3)+3  
  • *(c+3)访问"FIRST"也就是'F'的地址,
  • 最后+3跳过三个字符变成'S'的地址,输出从S开始到\0的字符串"ST" 
printf("%s\n", cpp[-1][-1] + 1);
  • 4、 cpp[-1][-1] + 1等于    *(*(cpp-1)-1)+1
  • 在第三段代码中未改变cpp,所以cpp指向的值与第二段代码中++cpp的值一样,指向cp第三个元素c+1
  • *(cpp-1) = *(c+2) = c+2
  • *( *(cpp-1) -1) +1 = *(c+2-1)+1 = *(c+1)+1
  • *(c+1)访问"NEW"的首元素'N'的地址,然后加1访问第二个字符'E'的地址,输出从E开始到\0的字符串"ER"。 

猜你喜欢

转载自blog.csdn.net/m0_73800602/article/details/132868387