【C语言学习笔记---学指针必刷它】

C语言进阶之必刷题

前言:
通过C语言指针进阶的知识,接下来挑战指针的必刷题。

/知识点汇总/

回顾:数组名的意义
数组名是数组首元素的地址,但是有两个例外:
1.sizeof(数组名),这里的数组名表示整个数组,sizeof(数组名)则计算的是整个数组的大小,单位是字节
2.&数组名,这里得数组名表示整个数组,&数组名取出的是整个数组的地址

1、一维数组笔试题

综述求打印的结果

1.1、第一题

#include <stdio.h>
int main()
{
    
    
	int a[] = {
    
     1,2,3,4 };
	printf("%d\n", sizeof(a));//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,⑥
	//a -- 类型:int       int *p = a;
	//&a --- 类型:int (* )[4]    int (*p)[4] = &a;
	printf("%d\n", sizeof(*&a));//16,⑦
	//sizeof(*&a) 等价 sizeof(a) ,相当于*&抵消了
	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,⑩
	//等价:&a[1] == &a[0]+1 == a+1
	return 0;
}

解释说明:

  1. 当数组名单独放在sizeof内部时,数组名表示的整个数组,则计算的是整个数组的大小,单位是字节
  2. 此时a并非单独放在sizeof内部,且也没有&操作符,所以此时数组名a是数组首元素的地址,a+0还是首元素的地址(只要是地址,不管什么类型的地址大小就是4/8,单位byte)
  3. a并非单独放在sizeof内部,也没有&,所以此时数组名是数组首元素地址,即a 就是首元素地址,*a解引用首元素大小就是4byte (*a == *(a+0) == a[0]等价)
  4. a并非单独放在sizeof内部,也没有&,所以此时数组名是数组首元素地址,则a+1是第二个元素的地址,a+1 == &a[1] 等价 ,是第二个元素的地址,地址大小就是4/8字节
  5. a[1]是数组第二个元素,则计算的就是第二个元素的大小,4byte
  6. &a — 表示取出整个数组的地址,整个数组的地址也是地址,则sizeof地址大小仍然是4/8,数组的地址 和 数组首元素的地址 ,本质区别是类型的区别,并非是大小的区别
  7. 对数组指针解引用访问一个数组的大小,单位是字节
  8. &a整个数组的地址,&a+1还是地址,但指针指向了整个数组的后面的地址了
  9. &a[0]取首元素的地址,地址大小就是4/8字节
  10. 取数组第二个元素的地址,地址大小就是4/8字节

1.2、第二题

#include <stdio.h>
int main()
{
    
    
	char arr[] = {
    
     'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));//6,数组名单独放在sizeof内部,计算的是整个数组的大小,单位是字节
	printf("%d\n", sizeof(arr + 0));//4/8,此时数组名为首元素的地址,地址的大小就是4/8字节,与类型无关
	//指针变量的大小与类型无关,不管什么类型的指针变量,大小都是4/8字节
	//指针变量是用来存放地址的,地址存放需要多大的空间,指针变量的大小就是几个字节
	//32位环境下,地址是32个二进制位,需要4个字节,所以指针变量的大小就是4个字节
	//64位环境下,地址是64个二进制位,需要8个字节,所以指针变量的大小就是8个字节

	printf("%d\n", sizeof(*arr));//1,此时arr表示首元素地址,*arr解引用就是首元素的大小1byte
	printf("%d\n", sizeof(arr[1]));//1,数组第二个元素的大小
	printf("%d\n", sizeof(&arr));//4/8,&arr取出整个数组的地址,地址的大小就是4/8
	printf("%d\n", sizeof(&arr + 1));//4/8,&arr取出整个数组的地址,&arr+1,得到整个数组之后的地址,地址的大小就是4/8
	printf("%d\n", sizeof(&arr[0] + 1));//4/8,得到第二个元素的地址,地址的大小就是4/8
	return 0;
}

1.3、第三题

#include <stdio.h>
#include <string.h>
//strlen:统计的是字符串中'\0',之前出现的字符的个数
int main()
{
    
    
	char arr[] = {
    
     'a','b','c','d','e','f' };
	//'a','b','c','d','e','f',注意不会自动补'\0'

	printf("%d\n", strlen(arr));//随机值,无法判断'\0'何时出现,另外此时arr是首元素的地址,从首元素地址开始计算

	printf("%d\n", strlen(arr + 0));//随机值,arr是首元素的地址,arr+0还是首元素地址

	printf("%d\n", strlen(*arr));//err,strlen参数必须是地址,不能参数值,此时*arr表示首元素'a' --ASCLL:  97
	//以strlen的角度,会认为被传参进去的'a' -- 97是地址,所以涉及到了非法访问,err报错

	printf("%d\n", strlen(arr[1]));//err,'b' -- 98 同理
	printf("%d\n", strlen(&arr));//随机值,&arr取出整个数组的地址,
	//类型是char (*)[6] -->实际接收会是 const char* 得到的是,首元素地址,所以依然会从首元素开始计数

	printf("%d\n", strlen(&arr + 1));//随机值,&arr取出整个数组的地址,+1,得到整个数组之后的地址,开始计数,但依然无法确定'\0',故随机值

	printf("%d\n", strlen(&arr[0] + 1));//随机值,同理,从第二个地址开始计数
	return 0;
}

1.4、第四题

#include <stdio.h>
int main()
{
    
    
	char arr[] = "abcdef";
	//"abcdef\0" -- 字符串的格式会自动约束结束标志符'\0'
	printf("%d\n", sizeof(arr));//7
	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;
}

1.5、第五题

#include <stdio.h>
#include <string.h>
//strlen 传参接收的是地址

int main()
{
    
    
	char arr[] = "abcdef";
	//"abcdef\0" -- 字符串的格式会自动约束结束标志符'\0'
	printf("%d\n", strlen(arr));//6,首元素地址
	printf("%d\n", strlen(arr + 0));//6,首元素地址
	printf("%d\n", strlen(*arr));//err,传参接收的是地址,非法访问
	printf("%d\n", strlen(arr[1]));//err,传参接收的是地址,非法访问
	printf("%d\n", strlen(&arr));//6
	printf("%d\n", strlen(&arr + 1));//随机值,得到的整个数组之后的地址开始计数,无法知道何时遇到'\0'
	printf("%d\n", strlen(&arr[0] + 1));//5,从第二个元素地址开始计数
	return 0;
}

1.6、第六题

#include <stdio.h>
int main()
{
    
    
	char* p = "abcdef";
	// "abcdef\0"
	//p指针变量指向的第一个字符的地址
	printf("%d\n", sizeof(p));//4/8,首元素的地址,计算的是指针变量的大小
	printf("%d\n", sizeof(p + 1));//4/8,p+1地址加1,还是地址
	printf("%d\n", sizeof(*p));//1,对首元素的地址解引用,*p == 'a'
	printf("%d\n", sizeof(p[0]));//1,p[0] == *(p+0) == *p 首元素的大小

	printf("%d\n", sizeof(&p));//4/8,&p,取的指针变量的新开辟空间的起始地址,地址大小就是4/8

	printf("%d\n", sizeof(&p + 1));//4/8,对指针变量p的地址加1,指向了指针变量空间地址的下一个地址,
	//与数组的元素无关,因为是指针变量p开辟的地址空间的指针偏移
	
	printf("%d\n", sizeof(&p[0] + 1));//4/8,&p[0]取出第一个元素的地址加1,得到第二个元素的地址
	//p+1 == &p[0] + 1
	return 0;
}

1.7、第七题

#include <stdio.h>
int main()
{
    
    
	char* p = "abcdef";
	// "abcdef\0"
	printf("%d\n", strlen(p));//6,首元素的地址,从首元素地址计数
	printf("%d\n", strlen(p + 1));//5,p+1地址加1,得到第二个元素地址,开始计数
	printf("%d\n", strlen(*p));//err
	printf("%d\n", strlen(p[0]));//err

	printf("%d\n", strlen(&p));//随机值,此时&p,取出的是指针变量的地址空间,与原数组元素的地址空间无关
	//所以无法确定从指针变量p的起始地址开始计数时,何时出现'\0'结束。故,随机值

	printf("%d\n", strlen(&p + 1));//随机值,对指针变量p的地址加1,指向了指针变量空间地址的下一个地址,
	//与数组的元素无关,因为是指针变量p开辟的地址空间的指针偏移
	//所以也无法确定'\0',故随机值

	printf("%d\n", strlen(&p[0] + 1));//5,&p[0]取出第一个元素的地址加1,得到第二个元素的地址,开始计数
	//p+1 == &p[0] + 1等价
	return 0;
}

2、 二维数组笔试题

二维数组理解:其实是数组的数组,是一维数组的数组

#include <stdio.h>
int main()
{
    
    
	int a[3][4] = {
    
     0 };
	printf("%d\n", sizeof(a));//48,数组名a单独放在了sizeof内存,表示整个数组,sizeof(a)计算的整个数组的大小,单位是字节。3*4*4 = 48
	printf("%d\n", sizeof(a[0][0]));//4,a[0][0]是数组的第一行第一个元素,计算的就是一个元素的大小
	printf("%d\n", sizeof(a[0]));//16,a[0]是第一行这个一维数组的数组名,数组名单单独放在了sizeof内部,
	//a[0]就表示整个第一行这个一维数组,sizeof(a[0])计算的整个第一行这个一维数组的大小

	printf("%d\n", sizeof(a[0] + 1));//4/8,a[0]并非单独放在sizeof内部,也没有&,所以a[0]表述二维数组首元素,也就是二维数组的第一行的数组的首元素地址
	//+1,也就是第一行第二个元素的地址,地址就是4/8
	//a[0] == &a[0][0]
	//a[0] + 1 == &a[0][1]

	printf("%d\n", sizeof(*(a[0]+ 1)));//4,a[0]并非单独放在sizeof内部,也没有& ,所以a[0]表述二维数组首元素,也就是二维数组的第一行的数组的首元素地址
		//+1,也就是第一行第二个元素的地址,再解引用得到第一行第二个元素的大小
	printf("%d\n", sizeof(a + 1));//4/8,a并非单独放在sizeof内部,也没有& ,所以a表述二维数组首元素,也就是第一行的数组名的首地址,地址+1,得到第二行的数组首地址,地址大小就是4/8.其中a的类型是:int (*)[4]的指针数组
	printf("%d\n", sizeof(*(a+1)));//16,a并非单独放在sizeof内部,也没有& ,所以a表述二维数组首元素,也就是第一行的数组名的首地址,再加1,得到第二行的数组首地址,最后解引用得到第二行的数组大小4*4
	//*(a+1) == a[1]
	//以这个角度理解:sizeof(a[1]),a[1]作为第二行的数组名,而数组名单独放在sizeof内部,则计算的是第二行的整个数组大小4*4

	printf("%d\n", sizeof(&a[0]+ 1));//4/8,a[0]是第一行的地址,&a[0]取出第一行一维数组的地址,地址加1,得到第二行的数组地址,类型是int (*)[4]
	printf("%d\n", sizeof(*(&a[0] + 1)));//16,同理,计算的第二行的数组元素大小
	printf("%d\n", sizeof(*a));//16,a并非单独放在sizeof内部,也没有&,a表示第一行的首地址,*a得到第一行的数组元素大小
	//*a == *(a+0) == a[0]

	printf("%d\n", sizeof(a[3]));//16,不会越界,因为sizeof内部是不会进行表达式的计算,只计算表达式的类型大小,所以这里a[3]表示第四行的数组名,sizoef计算的是第四行的整个数组大小4*4
	return 0;
}

3、指针笔试题

指针就是地址,单元编号 = 地址 = 指针,相互等价
指针大小、指针类型、字符指针、数组指针、函数指针、指向函数指针数组的指针…

3.1、第一题

#include <stdio.h>
int main()
{
    
    
	int a[5] = {
    
     1,2,3,4,5 };
	int* ptr = (int*)(&a + 1);
	//&a表示取出整个数组的地址,加1,跳过整个数组的地址,类型都是int (*)[4],因为取数组名的地址是数组指针类型(指针指向数组名)。
	//所以用int* 类型的指针变量接收需要将int (*)[4]强转为int*
	printf("%d, %d",*(a+1),*(ptr - 1));//2 , 5
	//*(a+1) == a[1]等价
	return 0;
}

3.2、第二题

#include <stdio.h>
//这里结构体大小是20字节
//基于X86环境下:
struct Test
{
    
    
	int Num;//4byte
	char* pcName;//4byte
	short sDate;//2byte
	char cha[2];//2byte
	short sBa[4];//8byte
}*p;

//假设p的值为0x100000,如下表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
//0x开头的是十六进制的数字
//p + 0x1表示p指针十六进制加1
int main()
{
    
    
	p = (struct Test*)0x100000;
	printf("%p\n", p + 0x1);//00100014,结构体指针加1,跳过20个字节,但注意20的16进制表示为0x14
	printf("%x\n", p + 0x1);//100014
	printf("%p\n", (unsigned long)p + 0x1);//0x100001,因为结构体类型被强转位整型,整型加1,就是加1即可
	printf("%p\n", (unsigned long*)p + 0x1);//0x100004,结构体被强转为无符号的指针类型,指针类型的p加1,则跳过4个字节即可
	return 0;
}

3.3、第三题

#include <stdio.h>
int main()
{
    
    
	int a[4] = {
    
     1,2,3,4 };
	int* ptr1 = (int*)(&a + 1);
	//&a取出整个数组的地址,地址加1,得到跳过整个数组之后的地址 -->ptr1也就是指向整个数组之后的地址,ptr[-1] == *(ptr-1)就得到了,a数组的最后一个元素4
	int* ptr2 = (int*)((int)a + 1);
	//a数组名表示数组首元素的地址,(int)a,将地址数据强转为int类型,再加1,整型数据加1就是加1,比如地址大小以十进制表示为152236,加1就是152237,这里以十六进制打印同理,直接加1即可
	//也就是说,从数组首元素的地址的第一个字节加1,得到第二个字节的地址,则ptr2就指向该位置,最后解引用4个字节,从第二个字节开始计4个字节,然后以%x格式输出(以%x格式不会保留,前置的0)
	// 1 2 3 4
	//小端存储:01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 
	//			a (a+1)== ptr2  再解引用4个字节:00 00 00 02,小端存储并输出格式为%x:02000000--->2000000(%x格式前置的0被省略)
	printf("%x , %x", ptr1[-1], *ptr2);//4, 200000
	//ptr1[-1] == *(ptr-1)
	return 0;
}

3.4、第四题

#include <stdio.h>
int main()
{
    
    
	int a[3][2] = {
    
     (0,1),(2,3),(4,5) };//逗号表达式
	//a[3][2]实际存放元素为:  {1,3,5};
	//[3][2]:
	//1 3
	//5 0
	//0 0
	int* p;
	p = a[0];
	//a[0] 表示第一行的数组名,表示第一行的首元素地址--> 1 3
	printf("%d", p[0]);//1 
	return 0;
}

3.5、第五题

#include <stdio.h>
int main()
{
    
    
	int a[5][5];
	int (*p)[4];//数组指针
	p = a;//类型合适?
	//虽然可以接收数组指针类型,但存在一定的类型差异
	//a -- int(*)[5];
	//p -- int(*)[4];
	//类型参数[]有差异,会有一个警告
	printf("%p,%d\n", &p[4][2] - &a[4][2],&p[4][2] - &a[4][2]);//FFFF FFFC , -4
	//&p[4][2] - &a[4][2] --->小地址减大地址 -4,%p是打印地址,认为地址中存储的补码就是地址,-4就是一个很大的数
	//1000 0000 0000 0000 0000 0000 0000 0100 --- -4原码
	//1111 1111 1111 1111 1111 1111 1111 1011 --- -4反码
	//1111 1111 1111 1111 1111 1111 1111 1100 --- -4补码
	//F    F    F    F    F    F    F    C    --- 十六进制表示FFFFFFFC
	//指针-指针 = 元素个数
	//%p是打印地址,认为地址中存储的补码就是地址
	return 0;
}

3.6、第六题

#include <stdio.h>
int main()
{
    
    
	int aa[2][5] = {
    
    1,2,3,4,5,6,7,8,9,10};
	int* ptr1 = (int*)(&aa + 1);
	//&aa取出整个数组的地址,加1,得到跳过整个数组之后的地址
	//*(ptr1-1)得到整个数组之后的前一个地址,也就是数组最后一个元素的地址,再解引用得到10
	int* ptr2 = (int*)(*(aa + 1));
	//aa表述数组名,二维数组的首元素地址是第一行的数组地址,加1,得到第二行的数组地址,再解引用得到第二行的元素,也就是5
	printf("%d,%d",*(ptr1 - 1),*(ptr2 - 1));//10,5
	return 0;
}

3.7、第七题

#include <stdio.h>
int main()
{
    
    
	char* a[] = {
    
     "work","at","alibaba" };
	//指针数组:指向每个字符串的第一个字符
	char** pa = a;
	//用pa二级指针接收a的地址,a地址又是第一个字符串的地址,所以是二级指针
	pa++;
	//pa一开始指向第一个字符串首地址
	//pa++则指向第二个字符串的首地址
	printf("%s\n", *pa);//at
	return 0;
}

3.8、第八题

#include <stdio.h>
int main()
{
    
    
	char* c[] = {
    
     "ENTER","NEW","POINT","FIRST" };
	char** cp[] = {
    
     c + 3,c + 2,c + 1,c };
	char*** cpp = cp;

	//cpp指向cp的首元素地址,cp指向c的首元素地址
	//++cpp指向cp的第二个元素的地址,cp的第二个元素地址,解引用得到(c+2),c+2指向c的第三个字符串首地址,解引用得到P
	printf("%s\n", **++cpp);//得到的P的地址 ---- POINT
	
	//cpp在上条语句执行后,指向cp+1的首元素地址,cp+1指向c的第三个字符串首元素地址
	//则++cpp指向cp的第三个元素的地址,解引用得到(c+1),c+1指向c的第二个字符串的首元素地址,
	//解引用的得到N,再减减得到C的第一个字符串首元素地址,解引用得到E,最后再+3,指针偏移3得到第一个字符串的第四个元素E
	printf("%s\n", *-- * ++cpp + 3);//得到的E的地址 -- EN

	//cpp[-2] == *(cpp -2)等价,表示在上条语句的基础上,cpp指向cp的第三个元素地址,然后cpp-2得到cp的首元素地址,
	//解引用得到c+3,而c+3指向了c的第四个字符串首元素地址,解引用得到了F的地址,最后加3,指针偏移3得到了第四个字符串第四个元素地址S
	printf("%s\n", *cpp[-2] + 3);//得到的是S的地址 - ST
	//*cpp[-2] + 3 === * *(cpp - 2) + 3等价

	//cpp[-1] == *(cpp -1)等价
	//cpp[-1][-1] == *(cpp[-1] -1)等价
	//cpp[-1][-1] == *(*(cpp - 1) - 1)等价
	//cpp[-1][-1] +1 == *(*(cpp - 1) - 1) + 1
	//所以cpp此时依然根据上条语句指向cp的第三个元素地址,然后cpp-1,则指向cp的第二个首元素地址,
	//解引用得到(c+2),(c+2)-1就得到了c+1,则c+1指向了c的第二个字符串的首字符地址,再解引用得到N,最后加1,得到E的地址
	printf("%s\n", cpp[-1][-1] + 1); //得到的是E的地址 --- EW
	//cpp[-1][-1] +1 == *(*(cpp - 1) - 1) + 1
	return 0;
}

4、结语

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

熟练的掌握指针有助于C语言之路得心应手
半亩方糖一鉴开,天光云影共徘徊。
问渠哪得清如许?为有源头活水来。–朱熹(观书有感)

猜你喜欢

转载自blog.csdn.net/m0_69455439/article/details/133499027