指针进阶版本


学习目标:

对指针方面的知识进一步深化
 



学习内容:

本章重点:

1:字符指针

2:数组指针

3:指针数组

4:数组传参和指针传参

5:函数指针

6:函数指针数组

7:指向函数指针数组的指针

8:回调函数

9:指针与数组面试题解析



一:字符指针

①:一般的字符指针:

#include 
int main()
{
	char c = 'w';
	char* pc = &c;//pc此时指向一个字符变量
	return 0;
}

②:字符串常量(存于常量区)

#include 
int main()
{
	char* pc = "abcdef";//表达式右侧为字符串常量,表达式意义在于将该字符串的
	//首字符的地址存至pc
	return 0;
}

note:如何识别字符串常量?

如果并没有开辟空间去存一个字符串,那系统会将其自动存至常量区,且在常量区:若代码中前后有两串字符串都没有开辟空间作存储,那么当它们都存进常量区之后,若是长得一摸一样,它俩其实是同一个常量,意思就是说,它俩的首字符地址是一样的。什么叫开辟空间存字符串?就比如:先申请一个数组空间,再把字符串寻进去,那此时就是叫开辟了空间去存一个字符串,那此时就不叫字符串常量了。如:

#include 
int main()
{
	char arr[] = "abcdef";//arr[]中依次存:'a','b','c','d','e','f','\0'
	return 0;
}

二:指针数组

①:不管是数组指针还是指针数组,强调的其实都是后面的对象,即前者本质是一个指针,它指向一个数组;而后者本质是一个数组,数组元素都是指针。

②:因为数组类型取决于,数组元素的类型和数组元素的个数,所以说指针数组也是多种多样的,比如:整型指针数组,字符指针数组等。当然,指针还有二级指针甚至三级指针之说,所以此时的数组又叫:二级整型指针数组等。举个例子:注意下述代码的p[i][j]不可与二维数组等同。

#include 
int main()
{
	int arr1[] = { 1,2,3,4 };
	int arr2[] = { 5,6,7,8 };
	int arr3[] = { 9,1,5,6 };
	int* p[] = { arr1,arr2,arr3 };//p先和[]结合成数组,去掉p[]剩下的就是
	//元素类型,即int*,arri为三个数组的数组名,一维数组的数组名就是数组首元素地址
	//所以p中存放的就是三个int*
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 3; j++)
		{
			printf("%d ", p[i][j]);//p[i]可取到第i个元素,即第i个数组的数组名
			//后头跟一个[j],就是数组名+[j],就是对每个数组的数组元素做访问啦
		}
	}
	return 0;
}

三:数组指针

①:由前述可知,这里的本质是一个指针,只不过它指向一个数组而已。

②:注意书写:(区分好p1,p2的本质)

int* p1[10];

int(*p2)[10];

p1将先于[10]结合生成一个数组,同样的道理,去掉变量名p1+[10],剩下的是int*,意思就是说:p1是一个数组,有10个元素,每个元素都是int*型。

p2将先与在同一括号内的*作结合变成一个指针,同样的道理,去掉变量名p2+*,剩下int [10],意思就是说:p2是一个指针,指向一个数组,这个数组的元素个数是10,每个元素都是int型。或者这样理解:*p2是不是在解引用?解引用得到p2空间内的东西,姑且将其视作一个变量,那此时是不是整体:int 变量名[10],这不就是一个最普通的数组吗。

③:int arr[10]的数据类型是啥?去掉变量名(数组名)后剩下的东西(顺序不变),就是这个变量的类型。即int [10]。int (*p)[10]的类型呢?为int (*)[10]就是它的类型。

④:数组名的二特殊:sizeof(arr)和&arr;除此之外都是代表数组的首元素地址,即使是二维数组也是!二维数组是把行看做单元,一行一个单元,那此时的二维数组就是一个一维数组,那第一行的数组指针就是这个二维数组的首元素地址!上一个例题加深印象:

#include 
int main()
{
	int arr[] = {1,2,3,4,5,6,7,8,9,0};
	int(*p)[10] = &arr;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", (*p)[i]);//记住:取谁(一个变量名)的地址存起来了,解引用就是这个变量名!
		//那*p就是arr,arr是数组名,上来就判断是不是二特殊!?这里是arr[i]不属于二特殊,那这个数组名就是代表数组首元素地址
		//那这个地址是普通的指针还是数组指针就看这个数组名是一维数组的数组名,还是二维数组的数组名,这里是一维
		//所以代表普通指针,且在一维数组中,数组名等价于数组首元素地址,所以(*p)[i]就是arr[i]就是在遍历数组元素。
	}
	return 0;
}

⑤:二特殊将决定一个指针到底是普通指针还是数组指针,这就涉及到指针类型的存在意义(解引用的和能够访问的字节数)!

⑥:二维数组可以视作一个只有一行的二维数组,有什么意义?上代码:

#include 
void print(int(*p)[10], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", p[0][i]);//二维数组arr[i][j]使用时:
		//arr[i]表示行号为i-1的这个行一维数组的数组名!
	}
}
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,0 };
	int(*p)[10] = &arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	print(p, sz);
	return 0;
}

四:对数组指针的使用

①:直接上代码:

#include 
void print(int(*p)[5], int r, int c)//为什么写成int(*p)[5],不懂再把前面看一遍
{
	int i = 0;
	for (i = 0; i < r; i++)
	{
		int j = 0;
		for (j = 0; j < c; j++)
		{
			/*printf("%d ", p[i][j]);*///第一种:p[i]等价于arr[i]为i-1行的数组名,后面是一维数组用法
			/*printf("%d ", (*(p + i))[j])*/;//第二种:因为p[i]=*(p+i);且(p+i)若与*不打括号,就会先跟[j]结合
			/*printf("%d ", *(p[i] + j));*///第三种:因为p[i]为数组名
			printf("%d ", *(*(p + i)+j));//第四种,与上述同理,为什么有四种:二二得四啊!
		}
	}
}
int main()
{
	int arr[3][5] = { 1,2,3,4,5,2,3,4,5,6,6,7,8,9,0 };
	int r = 3;//行
	int c = 5;//列
	print(arr, r, c);
	return 0;
}

五:数组传参、指针参数

①:一维数组:要么形参写的和实参一致(数组元素个数可省略),要么写出它的本质:如果传过来是数组首元素地址,那就用普通指针接收,如果穿过来的是整个数组的地址,那就用数组指针接收。注意:如果实参本身就是一个指针数组,单传一个数组名过来,此时是数组搜元素地址,而首元素本身就是一个指针(地址),那我们是不是要用二级指针去接收了呢!?一级还是二级指针不会改变它还是一个普通指针的身份,所以此时依然可以省略中括号内的值(形参)。哦,对了!形参的数组名爱写啥写啥,懂的都懂。

②:二维数组传参:要么写成和实参的那个二维数组创建时的形式一致(可省略行号,不可省略列号!)目前没碰到把一个二维数组的整个数组的地址传过来的情况,所以一般的将二维数组的数组名传过来,这里属于非二特殊,所以它代表二维数组首元素地址,即第一行的数组指针,形参不写列号,那此时的实参和形参的参数类型不一致咯!要么,写本质:二维数组首元素地址,即第一行的行一维数组的数组指针。

conclusion:形参的类型得根据,你实参传过来什么类型去写。

六:函数指针

①:函数名和&函数名都表示函数的地址,两者完全等价,没有数组那样的什么数组首元素地址之类的乱七八糟的东西。不要多加了()哈!

指针进阶版本_第1张图片

 ②:如何判定一个指针是不是函数指针?

第一步:它得是个指针,第二步:去掉指针变量名和左上角的*,剩下的东西若是一个函数的样子(除了函数名其它的该有的都有),那它就是一个函数指针。如:

#include  
void test()
{
	;
}
int main()
{
	void (*pf1)();
	void* pf2();
	return 0;
}

pf1和pf2是变量名,它俩谁能存test函数的地址呢?pf2不打括号,pf2先和()组函数咯!,此时pf2这个函数的返回值是一个万能指针。pf1确确实实是可以存test的地址的,不信你把(*pf1)去掉看看。

③:函数指针变量不用解引用,直接拿来作函数名即可。目的快速作函数调用,少打一个*

④:两个有趣的代码:

(*(void(*)())0)();
void(*signal(int, void(*)(int)))(int);

第一行解释:0天生为int,前面出现了函数指针类型,还打了括号,说明在作强制类型转换,那int变成了一个函数指针,再被解引用,可以得到一个函数,最右边再跟个(),说明作函数调用,函数无返回值,无参。

第二行解释:从里面往外面看:signal(int,void(*)(int))像函数声明,但是没写出这个signal函数的返回类型,那我把一个完整的函数声明去掉函数名,形参的类型定义,剩下的不就是函数的返回类型了吗,照做后剩下:void(*)(int),也就是说,signal函数的返回类型是函数指针,其类型就是照做后剩下的那一坨东西。

⑤:对函数指针的类型重命名:说函数指针的类型的时候是没有名字(函数名的),作重命名的时候把你想改成的函数指针类型新名字放在函数指针类型却名字的那个地方:

typedef void(*nb)(int);

nb就是void(*)(int)这个函数指针类型的新名字,哦对!最后的封号别忘记了。

用到第二行,可以换个写法:

typedef void(*nb)(int);
	nb signal(int, nb);

七:函数指针数组

①:数组元素得同类型,那这个函数指针数组的数组元素的类型得一样是吧。

②:举个栗子:

#include 
int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int main()
{
	int(*p[2])(int, int) = {Add,Sub};
	return 0;
}

③:用函数指针数组写一个辣鸡计算器:(整数的加减乘除)

#include 
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;
}
void menu()
{
	printf("**********************\n");
	printf("******1.Add 2.Sub*****\n");
	printf("******3.Mul 4.Div*****\n");
	printf("******0.exit**********\n");

}
int main()
{
	int (*p[5])(int, int) = {0,Add,Sub,Mul,Div};//写个0是方便用户使用,不然数组元素下标从0开始挺难受的
	int input = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		if (input >= 1 & input <= 4)
		{
			int x = 0;
			int y = 0;
			int ret = 0;
			printf("输入想要计算的两个数,以空格隔开:>");
			scanf("%d%d", &x, &y);
			ret = p[input](x, y);
			printf("%d\n", ret);


		}
		else if (0 == input)
		{
			printf("退出计算器\n");
			break;
		}
		else
			printf("输入错误\n");	
	} while (input);
	return 0;
}

八:指向函数指针数组的指针

①:看完看懂上面的是不是这样的就是一个函数指针数组:

int (*pf[10])(int ,int );

②:再产生一个指针指向上述的函数指针,就有了指向函数指针数组的指针:(插到*pf之间的一个指针):pf1是这么一种指针:

int (*(*pf1)pf[10])(int ,int );

③:再套娃下去意义不大,上面都懂了就行。

想看回调函数和指针练习题的看我下一篇文章吧。嘿嘿


学习时间:

2021.10



学习产出:


1、 技术笔记 2 遍
2、CSDN 技术博客 1 篇
3、 码云仓库

你可能感兴趣的:(c语言)