《C语言初阶进阶完整教程》- 已完结 - 指针进阶(详解+习题)

⭐️本篇博客我要给大家分享一下指针的进阶使用和相关习题。希望对大家有所帮助。
⭐️ 博主码云gitee链接:码云主页

目录

前言

🌏一、字符指针

        🍯示范1:

        🍯示范2: 

🌏二、指针数组

🍯示例一:

🍯示例二: 

🍯int* arr[]和二维数组的不同 

🌏三、数组指针

🍯示例一:

🍯arr和&arr的区别 

 🍯示例二:

🌏四、数组参数、指针参数

       🍯 1.一维数组传参

       🍯 2.二维数组传参

       🍯3.一级指针传参

       🍯4.二级指针传参

🌏五、函数指针

🌏六、函数指针数组  

🌏七、指向函数指针数组的指针

🌏八、回调函数

🌏九、指针和数组笔试题解析

🍤一维数组之sizeof笔试题

 🍤字符数组1sizeof笔试题

🍤字符数组1strlen笔试题 

🍤字符数组2sizeof笔试题

🍤字符数组2strlen笔试题 

🍤常量字符串sizeof笔试题

🍤常量字符串strlen笔试题  

🍤 二维数组笔试题

🌏十、指针笔试题

        🍤笔试一

        🍤笔试二

        🍤笔试三

        🍤笔试四

        🍤笔试五

        🍤笔试六

        🍤笔试七

        🍤笔试八

总结


前言


🌏一、字符指针

        🍯示范1:

int main()
{
	char ch = 'w';
	char* p= &ch;
	
	char* pa = "abcdef";
	return 0;
}

🍤字符变量ch里面存放了字符w,

🍤字符指针p里面存放了ch的地址,指针变量pa里面存放了abcdef字符串的地址,我们并没有对这个字符串进行初始化。而是直接把它放进了指针变量pa里面。字符w不同, abcdef是z字符常量,存储在内存中的只读数据区,无法修改,即便通过指针也无法修改。存放的只是这个字符串的首元素地址 ,改成数组存储就可以了,所以改成一下写法

const char* p2 = "hello bit";

        🍯示范2: 

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

	const char* str1 = "abcdef";
	const char* str2 = "abcdef";

	if (arr1 == arr2)
		printf("arr1==arr2\n");
	else
		printf("arr1!=arr2\n");

	if (str1 == str2)
		printf("str1==str2\n");
	else
		printf("str1!=str2\n");

	return 0;
}

 结果:

🍤这里str3和str4指向的是一个同一个常量字符串,既然字符串abcdef无法被改变,编译器也没有必要创建两个独立的内存空间来存放两个相同的字符串所以str1和str2的地址是完全一样的。而arr1和arr2是完全不同的数组,它们的首元素地址不一样,打印的结果是不相等

🌏二、指针数组

🍤整型数组的元素都是int类型,指针数组的元素都是指针变量

🍯示例一:

int main()
{
	char* arr[] = { "abcdef", "qwer", "zhangsan" };
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);

	for (i = 0; i < sz; i++)
	{
		printf("%s\n", arr[i]);
	}
	return 0;
}

🍯示例二: 

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };

	int* arr[] = {arr1, arr2, arr3};
	//arr是指针数组
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);//*(*(arr+i)+j)
		}
		printf("\n");
	}

	return 0;
}

🍯int* arr[]和二维数组的不同 

🍤二维数组首元素地址就是第一行地址,此时将二维数组的第一行看作一维数组里面的一个元素就可以了

	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };
	//arr1 arr2 arr3是二维数组里面的每一行
	int* arr[] = {arr1, arr2, arr3};
	//arr等价于一个二维数组,但有不同

🍤int*arr数组里的元素arr1,arr2,arr3的地址是连续存放的 

🍤但arr1 arr2 arr3对应的各自的数组内数据并不连续

🌏三、数组指针

int *p1[10];
int (*p2)[10];

🍤p1是指针数组,每个元素的类型是int*
🍤p2是数组指针,每个元素的类型是int()[10]

🍤[ ]的优先级高于*,所以必须加上()来保证p2先和*结合

🍯示例一:

int* parr[6];
int* (*pp)[6] = &parr;

🍯arr和&arr的区别 

 

🍤 arr代表首元素地址,&arr代表取出整个元素的地址,两者的值看上去是相同的但是意义不一样,arr+1跳过一个元素,&arr+1跳过一个数组

int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p1 = arr;
	int* p3 = &arr;
	int(*p2)[10] = &arr;

两者的存储方式也不一样

 🍯示例二:

int(*parr3[10])[5];

🍤[ ]的优先级高于*

🍤 parr3先和[结合,说明parr3是一个数组
🍤该数组有10个元素,每一个元素都是一个数组指针,类型是int(*)[5]
🍤该数组指针指向的数组有5个int类型的元素

🌏四、数组参数、指针参数

       🍯 1.一维数组传参

include <stdio.h>
void test(int arr[])
{}
void test(int arr[10])
{}
void test(int *arr)
{}
void test2(int *arr[20])
{}
void test2(int **arr)
{}
int main()
{
	int arr[10] = {0};
	int *arr2[20] = {0};
	test(arr);
	test2(arr2);
    return 0;
}

🍤形参使用数组的形式来接收,正确
🍤同样是数组的形式,正确
需我汪意的是,数组传参并不会开辟一个新的数组
所以函数[ ]里的数字是多少并不影响
🍤数组名是首元素的地址,用指针接收,正确
🍤 arr2是一个指针数组,(int *arr[20])和原数组对应,正确
🍤数组名是首元素地址,arr2的首元素是一个int*类型
可以用二级指针来接收,正确!

       🍯 2.二维数组传参

void test(int arr[3][5])//一一对应,正确!
{}
void test(int arr[][])//省略列,错误!
{}
void test(int arr[][5])//可以省略行,正确!
{}
void test(int *arr)//二维数组的首元素是第一行
{}//第一行是int(*)[5]类型,错误!
void test(int* arr[5])//指针数组,错误!
{}
void test(int (*arr)[5])//数组指针,正确!
{}
void test(int **arr)//arr不是一级指针的地址,错误!
{}

int main()
{
	int arr[3][5] = {0};
    test(arr);
    return 0;
}

🍤二维数组的首元素是第一行
🍤二维数组在定义的时候可以省略行,不能省略列

       🍯3.一级指针传参

#include <stdio.h>
void print(int *p, int sz)//用int*来接受
{
 int i = 0;
 for(i=0; i<sz; i++)
 {
 printf("%d\n", *(p+i));
 }
}
int main()
{
 int arr[10] = {1,2,3,4,5,6,7,8,9};
 int *p = arr;
 int sz = sizeof(arr)/sizeof(arr[0]);
 
 print(p, sz);//一级指针p,传给函数
 return 0;
}

       🍯4.二级指针传参

#include <stdio.h>
void test(int** ptr)
{
 	printf("num = %d\n", **ptr); 
}
int main()
{
 	int n = 10;
 	int*p = &n;
 	int **pp = &p;
	test(pp);
	test(&p);
	return 0;
}

🌏五、函数指针

🍤函数也有自己的地址,函数名/&函数名就是函数的地址

        🍍书写函数指针

int Add(int x, int y)
{
	return x + y;
}
int (*pf)(int, int) = Add;

🍤确定函数的返回类型
🍤确定函数的参数类型和个数
🍤把函数参数类型里的变量名去掉,放入括号里
(int x ,int y)去掉x、y,即(int,int)
🍤在前面加上函数的返回类型
🍤最后加上(*),以及函数指针变量名
 需要注意的是,(*pf)的括号不能省略,否则编译器会报错

 去掉括号之后就相当于函数声明,无法赋值

        🍍函数指针用法

 🍤(*pf)其实就相当于my_test

         🍍函数指针调用函数

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int (* pf)(int, int) = Add;
	int sum = (*pf)(2,3);
	int sum1 = pf(2, 3);

	int sum2 = Add(2, 3);
	printf("%d\n", sum);
	printf("%d\n", sum1);
	printf("%d\n", sum2);

	return 0;
}

        🍍结果:

 🍤因为我们已经把Add的地址转给了pf指针,函数名Add和指针pf实际上是等价的
所以在使用函数指针的时候,可以不带*使用。但是带*的时候一定要加括号

🌏六、函数指针数组  

        🍍基本类型

int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Mul(int x, int y)
{
	return x * y;
}
int Div(int x, int y)
{
	return x / y;
}

int main()
{
	int (*pfArr[4])(int, int) = {Add, Sub, Mul, Div};//函数指针数组
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		//int ret = (*pfArr[i])(8, 4);
		int ret = pfArr[i](8, 4);

		printf("%d\n", ret);
	}
	return 0;
}

         🍍转移表

int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;

	int (*pfArr[5])(int, int) = {0, Add, Sub, Mul, Div};
    //pfArr是一个函数指针的数组,也叫转移表
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("退出计算器\n");
			break;
		}
		else if (input >= 1 && input <= 4)
		{
			printf("输入2个操作数:>");
			scanf("%d %d", &x, &y);
			ret = pfArr[input](x, y);
			printf("ret = %d\n", ret);
		}
		else
		{
			printf("选择错误\n");
		}
	} while (input);

	return 0;
}

🌏七、指向函数指针数组的指针

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int (*pa)(int, int) = Add;//函数指针
	int (* pfA[4])(int, int);//函数指针的数组
	int (* (*ppfA)[4])(int, int) = &pfA;
    //ppfA 是一个指针,该指针指向了一个存放函数指针的数组

	return 0;
}

🍤函数指针数组是一个数组,数组可以用数组指针来存放地址
🍤指向函数指针数组的指针:是一个指针
🍤该指针指向一个数组,数组的每个元素都是一个函数指针 

🌏八、回调函数

🍤回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一 个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该 函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或 条件进行响应。

🌏九、指针和数组笔试题解析

🍤一维数组之sizeof笔试题

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

 🍤字符数组1sizeof笔试题

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

🍤字符数组1strlen笔试题 

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

 strlen(*arr)

//err, strlen需要的是一个地址,从这个地址开始向后访问找字节,直到\0,统计字符的个数。但是*arr始数组首元素,也就是‘a’,这是传给strlen的就是‘a’的ascii码值是97,strlen函数会把97作为起始地址,统计字符串,会形成内存访问冲突。

strlen(&arr):

//&arr是arr数组的地址,虽然类型和strlen的参数类型有所差异,但是传参过去后,还是从第一个字符的位置开始向后数字符,结果是随机值

🍤字符数组2sizeof笔试题

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

🍤字符数组2strlen笔试题 

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

🍤常量字符串sizeof笔试题

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

🍤常量字符串strlen笔试题  

char *p = "abcdef";
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));

🍤 二维数组笔试题

int a[3][4] = {0};
printf("%d\n",sizeof(a));//数组名单独放在sizeof内部,计算的是整个数组的大小
printf("%d\n",sizeof(a[0][0]));
printf("%d\n",sizeof(a[0]));//a[0]作为第一行的数组名,没有&,没有单独放在sizeof内部,所以a[0]表示的是首元素的地址,即a[0][0]的地址,a[0]+1就是第二个元素地址
printf("%d\n",sizeof(a[0]+1));
printf("%d\n",sizeof(*(a[0]+1)));
printf("%d\n",sizeof(a+1));//a是二维数组的数组名,没有&,没有单独放在sizeof内部,a表示首元素的地址,即第一行的地址,a+1就是第二行的地址,是类型为int(*)[4]的指针
printf("%d\n",sizeof(*(a+1)));//*(a+1)就是第二行,相当于第二行的数组名,*(a+1)->a[1],sizeof(*(a+1))计算的是第二行的大小
printf("%d\n",sizeof(&a[0]+1));
printf("%d\n",sizeof(*(&a[0]+1)));//相当于a[1]
printf("%d\n",sizeof(*a));//二维数组的数组名,没有&,没有单独放在sizeof内部,a表示首元素的地址,*a就是二维数组的首元素,也就是第一行*a->*(a+0)->a[0]
printf("%d\n",sizeof(a[3]));

 

🌏十、指针笔试题

        🍤笔试一

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没有进行&和sizeof的操作,所以这里a代表的数数组首元素地址,加1跳过一个整形得到如图所示的位置,所以*(a+1)是a[1]=2;
🍤a[5]是一个数组,&a代表整个数组的地址,&a的类型是int (*)[5],&a+1跳过一个数组,得到如图所示的位置,即a+4
🍤将&a+1的类型强制类型转换为(int*),赋值ptr,ptr-1跳过一个整形,得到如图所示的位置,即a+4,所以*(ptr-1)=a[4]=5
所以输出结果为2,5.

        🍤笔试二

struct Test
{
	int Num;
	char *pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
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, p是结构体类型指针,加上1,跳过一个结构体(20个字节),为0x100014;将p强制类型转换为unsigned long后,p就是一歌无符号的整形,加1就是加1,即Ox100001 ;将p强制类型转换为unsigned int*后,加1跳过一个unsigned int类型(4个字节),为0x100004.

结果:

        🍤笔试三

//当前机器为小端存储模式
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;
}

🍤 &a是整个数组的地址,加1跳过1个数组,得到如图所示的位置ptr1,类型被强转为int*,ptrl[-1]即*(ptr1-1), ptr1减1,往做移动1个int类型的大小,即a[3]=4;
a被强转为int类型的大小,加1地址加1,指向的位置如图所示,该地址又被强转赋给ptr2,*ptr2访问的是四个字节的大小,依次读取00 00 00 02,由于是小端存储模式,即0x02000000。

 结果:

        🍤笔试四

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

🍤p是int类型的指针,a[0]是二维数组第一个元素,也是第一行的以为数组的数组名,没有进行&和sizeof的操作,所以这里的a[o]代表的是二维数组第一行首元素地址,即a[o][0]的地址,p[0]等价于*(p+0),为1.
结果:

        🍤笔试五

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

🍤指针p是int(*)[4]类型,是一个指向的是四个元素的数组指针,如图,&p[4][2]和&a[4][2]相差4,%d打印就是-4
-4
🍤原码 10000000 00000000 00000000 00000100反码11111111 11111111 11111111 11111011补码11111111 11111111 11111111 11111100%p打印就是0xfffffffc

结果:

        🍤笔试六

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 main()
{
	char *a[] = {"work","at","alibaba"};
	char**pa = a;
	pa++;
	printf("%s\n", *pa);
	return 0;
}

🍤a是一个指针数组,每个指针指向的是一个常量字符串,pa指针指向的是数组a首元素,pa++跳过一个char*类型,位置由①变到②,*pa等价于a[1],即字符串"at"中a的地址,%s打印就是at.

        🍤笔试八

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

 

🍤第一次解引用得到cp[1],第二次解引用得到c[3]打印出来就是POINT 

🍤 *++cpp得到cp[2]
在--得到c, j解引用得到c[0]——"ENTER"再加3就是ER

结果:


总结

         

        🍤现在是11点多了,刚好写玩,你在干什么呢!?

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

penguin_bark

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值