学C的第二十七天【指针的进阶(三)】

=========================================================================

相关代码gitee自取C语言学习日记: 加油努力 (gitee.com)

 =========================================================================

接上期

学C的第二十六天【指针的进阶(二)】_高高的胖子的博客-CSDN博客

 =========================================================================

                     

复习巩固:

数组名:

数组名数组首元素的地址

但是有两个例外

                

1 . sizeof(数组名) :这里的数组名表示整个数组计算的是整个数组的大小单位字节

                

2 . &数组名 :这里的数组名表示整个数组取出的是整个数组的地址

                  

9. 指针和数组笔试题解析

补充(回顾):

  • sizeof() 是一个运算符,而 strlen() 是一个函数
  • sizeof() 计算的是变量或类型所占用的内存字节数,而 strlen() 计算的是字符串中字符的个数
  • sizeof() 可以用于任何类型的数据,而 strlen() 只能用于以空字符 '\0' 结尾的字符串
  • sizeof() 计算字符串的长度包含末尾的 '\0'strlen() 计算字符串的长度不包含字符串末尾的 '\0'

                  

(1). 一维数组相关题:

           

对应代码:

#include <stdio.h>
int main()
{
	int a[] = { 1,2,3,4 };

	printf("%d\n", sizeof(a));
	//特殊情况,这里的a为整个数组,为4*4=16字节

	printf("%d\n", sizeof(a + 0));
	//数组名a是首元素地址,a+0还是首元素地址,地址的大小为4/8字节

	printf("%d\n", sizeof(*a));
	//数组名a为首元素地址,*a就是首元素,大小是4个字节

	printf("%d\n", sizeof(a + 1));
	//数组名是首元素地址。a+1是第二个元素地址,为4/8字节

	printf("%d\n", sizeof(a[1]));
	//a[1]是数组的第二个元素,为4个字节

	printf("%d\n", sizeof(&a));
	//&a是整个数组的地址,数组地址也只是个地址,为4/8字节

	printf("%d\n", sizeof(*&a));
	//解引用整个数组的地址,相当于sizeof(a),为16个字节

	printf("%d\n", sizeof(&a + 1));
	//相当于跳过了整个数组,虽然跳过了整个数组,&a+1依然是个地址,为4/8个字节

	printf("%d\n", sizeof(&a[0]));
	//取数组首元素的地址,为4/8个字节

	printf("%d\n", sizeof(&a[0] + 1));
	//&a[0]是首元素地址,&a[0]+1就是第二个元素的地址,是地址就为4/8个字节

	return 0;
}

                

(2). 字符数组和指针相关题:

(2.1). 明确赋值的字符数组(无结束符\0):

                

对应代码:

char arr[] = { 'a','b','c','d','e','f' };
	//六个字符的字符数组,没有结束符\0
	
	printf("%d\n", sizeof(arr));
	//数组名单独放在sizeof中,这里的arr为整个数组,计算的是整个数组大小,为6个字节 -- char[6]

	printf("%d\n", sizeof(arr + 0));
	//arr表示数组首元素地址,arr+0还是数组首元素地址,是地址就为4/8个字节 -- char*
	
	printf("%d\n", sizeof(*arr));
	//arr表示数组首元素地址,*arr就是首元素,为1个字节 -- char

	printf("%d\n", sizeof(arr[1]));
	//arr[1]就是第二个元素,为1个字节 -- char

	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));
	//为第二个元素的地址,为4/8个字节

	printf("%d\n", strlen(arr));
	//因为字符数组arr中没有\0,所以在求字符串长度时,
	//会一直往后数到未知的\0为止,是不确定的,所以会产生随机值

	printf("%d\n", strlen(arr + 0));
	//arr+0是首元素地址,所以和上面的一样会产生随机值

	printf("%d\n", strlen(*arr));
	//arr是数组首元素地址,*arr是数组的元素,是 'a' ,ACSII码为97,
	//所以strlen会从 '97' 的地址开始统计字符串长度,为非法访问内存

	printf("%d\n", strlen(arr[1]));
	//arr[1]为数组首元素,即 'a' ,所以和上面一样为非法访问内存

	printf("%d\n", strlen(&arr));
	//&arr是数组地址,数组地址和数组首元素的地址,两者的值是一样的,
	//数组地址类型char*[6],会强制转化为char*类型,所以依然从数组的首元素开始向后统计,结果为随机值

	printf("%d\n", strlen(&arr + 1));
	//&arr+1为跳过这个数组,所以从这个数组后的地址开始统计,结果为随机值

	printf("%d\n", strlen(&arr[0] + 1));
	//&arr[0]+1是第二个元素的地址,往后统计直到遇到\0,结果为随机值

                 

(2.2). 未明确赋值的字符数组(有结束符\0):

              

对应代码:

	char arr[] = "abcdef";
	//未明确定义的字符数组,有结束符\0
	//		{a b c d e f \0}

	printf("%d\n", sizeof(arr)); 
	//sizeof(数组名):计算整个数组的大小,为7个字节,包含\0 -- char [7]

	printf("%d\n", sizeof(arr + 0));
	//arr+0是首元素的地址,是地址就是4/8个字节 -- char*

	printf("%d\n", sizeof(*arr));
	//*arr为数组首元素,为1个字节 -- char

	printf("%d\n", sizeof(arr[1]));
	//arr[1]为第二个元素,为1个字节 -- char

	printf("%d\n", sizeof(&arr));
	//&arr是数组的地址,是地址就是4/8个字节 -- char* [7]

	printf("%d\n", sizeof(&arr + 1));
	//&arr为跳过arr这个数组后的地址,是地址就是4/8个字节 -- 随机地址

	printf("%d\n", sizeof(&arr[0] + 1));
	//&arr[0]+1是第二个元素的地址,是地址就是4/8个字节 -- char*

	printf("%d\n", strlen(arr));
	//统计到\0为止,即 a b c d e f ,所以结果为6

	printf("%d\n", strlen(arr + 0));
	//和上题一样,都是从数组首元素开始,\0结束,所以结果为6

	printf("%d\n", strlen(*arr));
	//strlen应该接收一个地址,从这个地址向后计算长度,
	//这里接收的是数组首元素字符'a'(char),不是地址,所以会报错

	printf("%d\n", strlen(arr[1]));
	//这里和上面一样接收的也是数组首元素字符'a',不是地址,会报错

	printf("%d\n", strlen(&arr));
	//接收数组指针char*[7],但是会被强制转化为const char*类型,
	//值还是首元素的地址,所以统计的长度还是6

	printf("%d\n", strlen(&arr + 1));
	//&arr+1是跳过这个数组后的一个地址,从这个地址往后不知道什么时候后有\0,所以结果为随机值

	printf("%d\n", strlen(&arr[0] + 1));
	//&arr[0]+1为数组的第二个元素地址,从后计算长度直到\0,所以结果为5

              

(2.3). 未明确赋值的字符指针(有结束符\0):

             

对应代码:

	char* p = "abcdef";
	//这样写是把字符串的首字符地址放入指针中,即‘a’的地址

	printf("%d\n", sizeof(p));
	//p为一级指针,存放的是地址,是地址就是4/8个字节

	printf("%d\n", sizeof(p + 1));
	//p+1是字符'b'的地址,是地址就是4/8个字节

	printf("%d\n", sizeof(*p));
	//相当于‘b’的地址,是地址就是4/8个字节

	printf("%d\n", sizeof(p[0]));
	//p[0]相当于*(p+0),即*p,为字符‘a’,为1个字节

	printf("%d\n", sizeof(&p));
	//取出一级指针p的地址,相当于p的二级指针,存放的是指针p的地址,是地址就是4/8个字节

	printf("%d\n", sizeof(&p + 1));
	//相当于指针p地址的后面一个地址,是地址就是4/8个字节

	printf("%d\n", sizeof(&p[0] + 1));
	//相当于a的地址的后一个地址,即b的地址,是地址就是4/8个字节

	printf("%d\n", strlen(p));
	//指针p存放的是‘a’的地址,所以从a的地址往后数,所以长度为6

	printf("%d\n", strlen(p + 1));
	//p+1是‘b’的地址,所以长度为5

	printf("%d\n", strlen(*p));
	//*p是字符‘a’,strlen的参数应该是const char*指针,所以会报错

	printf("%d\n", strlen(p[0]));
	//字符串可以理解为一个字符数组,所以p[0]相当于字符‘a’,会报错

	printf("%d\n", strlen(&p));
	//&是一级指针p的地址,从该地址往后数直到\0,所以结果为随机值

	printf("%d\n", strlen(&p + 1));
	//从一级指针的地址的后一个地址开始数直到\0,结果为随机值

	printf("%d\n", strlen(&p[0] + 1));
	//相当于a的地址的后一个地址,即b的地址,直到\0,所以结果为5

                

(3). 二维数组相关题:

                

对应代码:

//二维数组
	int a[3][4] = { 0 };
	//创建三行四列的二维数组

	printf("%d\n", sizeof(a));
	//三行四列每个4个字节,所以一个是3*4*4=48字节

	printf("%d\n", sizeof(a[0][0]));
	//求该二维数组0行0列的元素大小,为4个字节

	printf("%d\n", sizeof(a[0]));
	//a[0]是二维数组的第一个元素地址,即第一行地址(第一行数组的数组名)
	//相当于把数组名放入sizeof中,所以计算的是整个数组的大小,为16个字节

	printf("%d\n", sizeof(a[0] + 1));
	//这里a[0]不是单独放在sizeof中的,所以是第一行数组的首元素地址,即a[0][0]地址
	//所以a[0]+1是第一行第二个元素的地址,是地址就是4/8个字节

	printf("%d\n", sizeof(*(a[0] + 1)));
	//相当于解引用第一行数组的第二个元素地址,元素是int类型,为4个字节

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

	printf("%d\n", sizeof(*(a + 1)));
	//a+1是第二行地址--int(*)[4],*(a+1)解引用后是4个int类型的元素
	//为16个字节

	printf("%d\n", sizeof(&a[0] + 1));
	//&a[0]是二维数组第一行数组地址--int(*)[4]
	//&a[0] + 1是第二行的地址,是地址就是4/8个字节

	printf("%d\n", sizeof(*(&a[0] + 1)));
	//&a[0] + 1是第二行的地址,解引用后是4*4=16个字节

	printf("%d\n", sizeof(*a));
	//*a是解引用二维数组首元素地址,即第一行地址,为4*4=16个字节

	printf("%d\n", sizeof(a[3]));
	//a[3]看起来是越界了,但sizeof()运行时是不会访问内存的(类型属性),只看类型
	//这里a[3]的类型是int[4],为4*4=16个字节
	
	//表达式都有两个属性:1.值属性  2.类型属性
	//代码运行时:编译+链接 --> 可执行程序 --> 运行 --> 结果
	//sizeof是在 编译 时就已经有结果了(类型属性),根据类型为16个字节
	//a[3]是在 运行 是才有结果(值属性),根据运行结果应该为数组越界报错

         

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

             

10. 指针笔试题

(1). 笔试题一:

             

对应代码:

#include <stdio.h>
int main()
{
	int a[5] = { 1, 2, 3, 4, 5 };

	int* ptr = (int*)(&a + 1);
	//&a+1: 跳过该数组后的地址,再强制转化成int*类型,方便后面移动时是4个字节

	printf("%d,%d", *(a + 1), *(ptr - 1));
	//*(a+1): 是数组的第二个元素 -- 2
	//*(ptr-1): ptr刚好在整个数组后,ptr-1后指针在 5 的地址位置,所以解引用后是5

	return 0;
}

                

(2). 笔试题二:

                

对应代码:

#include <stdio.h>
//结构体的大小是20个字节(x86)
struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}* p = (struct Test*)0x100000;//结构体指针变量
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
	printf("%p\n", p + 0x1); 
	//结构体指针 +1 跳过一个结构体(20个字节)
	//所以 结果 == 0x100000 + 0x14(20的十六进制数)
	//         == 0x100014

	printf("%p\n", (unsigned long)p + 0x1);
	//把原来的结构体指针的类型强制转化为(unsigned long)整数,
	//相当于普通的数值计算,+1 就是 +1
	// 结果 == 0x100000 + 0x1 == 0x100001

	printf("%p\n", (unsigned int*)p + 0x1);
	//转化为无符号整型指针,所以每次跳过4个字节(x86)
	// 结果 == 0x100000 + 0x4 == 0x100004


	return 0;
}

                

(3). 笔试题三:

                

对应代码:

#include <stdio.h>
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);
	//ptr1[-1] 等价于 *(ptr1-1)
	//%x : 以十六进制打印

	return 0;
}

                

(4). 笔试题四:

                

对应代码:

#include <stdio.h>
#include <stdio.h>
int main()
{
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };
	//注:这里用的是(),而不是{},()表达式的结果是最后一个
	//所以a的实际情况是:{1,3,5,0,0,0}

	int* p;
	p = a[0];
	//a[0]是二维数组的第一行第一列的地址,相当于a[0][0]

	printf("%d", p[0]); 
	//p[0] 相当于 *(p+0),即*p -- *a[0][0],解引用后为 1

	return 0;
}

                

(5). 笔试题五:

                

对应代码:

#include <stdio.h>
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]);
	//%d:
	//这里是小地址-大地址,两个相差4,又因为以%d打印
	//所以是 -4 (把补码转换为原码后打印)

	//%p:
	//以 %p 打印的话,就不考虑无符号和有符号了,直接以补码打印:
	//-4是负数,内存中存的是补码:
	//原码:10000000000000000000000000000100
	//反码:11111111111111111111111111111011
	//补码:1111 1111 1111 1111 1111 1111 1111 1100 -- 内存中存储的-4
	//      F	 F	  F    F    F    F    F    C

	return 0;
}

                

(6). 笔试题六:

                

对应代码:

#include <stdio.h>
int main()
{
	//二维数组:
	int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	//第一行: 1 2 3 4 5      第二行: 6 7 8 9 10

	int* ptr1 = (int*)(&aa + 1);
	//&aa+1:跳过整个二维数组后的当前位置的地址(10后面的位置)

	int* ptr2 = (int*)(*(aa + 1));
	//aa+1:跳过二维数组的首元素(第一行),指向第二行首元素地址
	//*(aa+1) 相当于 *aa[1][0],解引用后拿到第二行的首元素地址(6的位置)


	printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
	//ptr1-1:“10”后面的地址-1,指向了10的地址
	//ptr2-1:“6”的前一个地址,指向5的地址

	return 0;
}

                

(7). 笔试题七:

                

对应代码:

#include <stdio.h>
#include <stdio.h>
int main()
{
	char* a[] = { "work","at","alibaba" };
	//相当于用三个字符指针分别存放三个字符串:
	//char* p1 = "work" -- 存放w的地址
	//char* p2 = "at" -- 存放a的地址
	//char* p3 = "alibaba" -- 存放a的地址

	char** pa = a; //存放a的首元素"p1"的地址
	
	pa++;//指向"p2"的地址

	printf("%s\n", *pa);
	//*pa:解引用指针p2的地址,p2指向"a"的地址,以%s打印为 at

	return 0;
}

                

(8). 笔试题八:

                

对应代码:

#include <stdio.h>
int main()
{
	char* c[] = { "ENTER","NEW","POINT","FIRST" };
	//相当于用四个字符指针分别存放四个字符串:
	//(下标为0)char* p1 = "ENTER" -- 存放“E”的地址
	//(下标为1)char* p2 = "NEW" -- 存放“N”的地址
	//(下标为2)char* p3 = "POINT" -- 存放“P”的地址
	//(下标为3)char* p4 = "FIRST" -- 存放“F”的地址

	char** cp[] = { c + 3,c + 2,c + 1,c };
	//相当于用四个二级字符指针分别存放四个一级指针:
	//(下标为0)char** pp1 = c+3 = &p4 -- 存放一级指针p4的地址
	//(下标为1)char** pp2 = c+2 = &p3 -- 存放一级指针p3的地址
	//(下标为2)char** pp3 = c+1 = &p2 -- 存放一级指针p2的地址
	//(下标为3)char** pp4 = c = &p1 -- 存放一级指针p1的地址

	char*** cpp = cp;
	//三级指针存放二级指针cp首元素的地址,即pp1地址

	printf("%s\n", **++cpp);
	//cpp指向pp1地址,++后指向pp2地址,
	//第一次*得到pp2内容:p3的指针(地址)
	//第二次*得到p3内容:“P”的地址
	//所以打印 "POINT"
	
	//因为++,此时cpp已经指向了pp2,不是pp1

	printf("%s\n", *-- * ++cpp + 3);
	//先进行++,cpp指向pp3
	//再进行第一次*:得到pp3内容 -- p2的指针(地址)
	//再--:p2-1 ,指向p1
	//再进行第二次*:得到p1内容 -- “E”的地址("ENTER")
	//最后进行+3:“E”的地址+3,指向“ENTER”第二个“E”
	//所以打印"ER"

	//因为++,此时cpp已经指向了pp3,不是pp2

	printf("%s\n", *cpp[-2] + 3);
	//cpp[-2] 相当于 *(cpp-2)
	//所以题目可以写成:**(cpp-2)+3
	//先进行 cpp-2 : 从指向pp3转为指向pp1
	//再进行第一次*:得到pp1内容 -- p4的指针(地址)
	//再进行第二次*:得到p4的内容 -- “F”的地址(“FIRST”)
	//最后+3:指向“FIRST”的“S”的地址
	//所以打印“ST”

	//此时cpp还是指向pp3

	printf("%s\n", cpp[-1][-1] + 1);
	//cpp[-1] 相当于 *(cpp-1)
	//所以题目可以转化为:*(cpp-1)[-1]+1
	//再转化:*(*(cpp-1)-1)+1
	//先进行cpp-1:cpp从pp3转为指向pp2
	//再进行*:得到pp2内容 -- p3的指针(地址)
	//再进行-1:p3-1 -- 得到p2的地址
	//再进行*:得到p2的内容 -- “N”的地址(“NEW”)
	//最后+1:指向“NEW”的"E"
	//所以打印“EW”

	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_63176266/article/details/131678803