目录
一.字符指针
我们都知道字符指针是存放一个字符的地址的,但是请诸位看以下的代码:
这里是把一个字符串放到pstr指针变量里了吗?通过打印出来的结果,再结合打印%s遇到'\0'就会停止,所以我们猜测pstr存储的应该是字符串首字符的地址,然后向后打印,遇到字符串的\0终止。事实其实的确如此,就是把一个常量字符串的首字符 h 的地址存放到指针变量 pstr 中。
我们再来看一下的代码:
这串代码的输出结果是什么呢?在笔者看来结果应该是 str1!=str2并且str3!=str4,
结果却是str1!=str2,str3=str4,那么可以推测str1和str2的地址不相同,而str3和str4的地址相同,那么为什么呢?
这里还是要归结到计算机的存储,在计算机看来,str1和str2是开辟的两个数组,是可修改的,所以地址不可以相同,反过来想,如果地址相同,那么我修改str1的内容,str2的内容也会被修改,这显然是不合适的。那str3和str4又如何解释呢?首先str3和str4存放的是常量字符串首字符h的地址,对于计算机来说,常量字符串一般是不做修改的,所以如果就把相同的常量字符串存到同一块空间,即指针指向同一个地址,这样节省了空间,这也是相当合理的。
二.指针数组
指针数组,顾名思义,就是存放指针的数组。
例如常见的指针数组有:
常见的应用:
我们很轻易就可以利用它实现二维数组的功能。
它也可以:
看到这里,可能有的朋友就要问了,这和二维数组不一模一样了吗?不,它们的区别在于,指针数组所存的三个数组的地址是不连续的,而在二维数组是连续存放的。通过调试可以发现:
三.数组指针
显而易见,数组指针就是指向数组的指针。
我们都知道&数组名和数组名指向的都是数组首元素的地址,但是它们的类型和表达的含义是不同的,&数组名是取出整个数组的地址,应该用一个数组指针指向,而数组名仅用一个相同类型的指针指向即可。
我们发现arr和&arr的地址符合我们所说的,但是arr+1和&arr+1所展现的地址就不相同了,而且我们很惊奇的发现&arr+1和&arr相差恰好12个字节,这是巧合吗?
这里就不得不谈到地址+1 的问题,我们知道地址+1跳过的是这个地址所属类型的大小。所以我们分析一下,就可以发现,arr是数组首元素的地址是int*类型,所以加1跳过4/8(取决于编译环境)个字节,而&arr取出整个数组的地址,它的类型是int*[3],所以+1要跳过整个数组大小。
数组指针的应用:
#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf("%d ", arr[i][j]);
}
}
}
void print_arr2(int(*arr)[5], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };
print_arr1(arr, 3, 5);
//数组名arr,表示首元素的地址
//但是二维数组的首元素是二维数组的第一行
//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
//可以数组指针来接收
print_arr2(arr, 3, 5);
return 0;
}
我们发现,学习了数组指针,我们在面对二维数组的时候有了两种传参方式,一种是int arr[3][5],即以数组的形式,另一种是int(*arr)[5],以数组指针的形式传参,也就是我们在有些地方看到的行指针。
四.数组参数
1.一维数组传参
上面几种传参方式都是可以的,以此我们发现怎么设计传参,本质上是要看传过来的是什么类型。
2.二维数组传参
对于二维数组,我们要始终谨记,它的数组名表示第一行元素的地址,传参时函数形参的设计只能省略第一个[]的数字,对于一个二维数组,可以不知道它有多少行,但是必须知道它每一行有多少元素,对于更高维的数组也是如此,第一个[]的数字传参时可以省略,其余不能省略。
五.函数指针
我们知道,指针是指向地址的,函数指针,难道函数还有指针?
显然,函数是由地址的。我们怎么理解呢?
其实,函数指针和数组指针类比是比较相似的。
如果说指向数组的指针是数组指针,那么指向函数的指针就是函数指针.
那么,怎么存储函数的地址呢?表现形式是什么呢?
比如上述的Add函数,怎么存储这个地址呢?
我们是这样表示的:int (*pf)(int,int)=&Add;这里的第一个int是这个函数的返回类型,(*pf)说明它是个指针,括号(int,int)是函数两个参数的类型。
常见用法:
下面来看两端有趣的代码:
(*(void(*)())0)();
void(*signal(int,void(*)(int)))(int);
这两串代码均出自于《C陷阱与缺陷》这本书,感兴趣的朋友可以去看看。
那么我们先来看一下第一个代码,这段代码我们可以这样解读:
经过层层剖析,我们可以解读这样看起来比较麻烦的代码。再来看第二个代码
这样解析可能比较麻烦,那么有没有一种方法可以简化一下呢?我们可以借助typedef重定义来简化。
笔者写了这些,可能对于大家来说还是决定函数指针比较鸡肋,接下来笔者要说一下函数指针的用途。
①.函数指针的用途
我们可以简易写一个计算器实现加减乘除的功能。
#include<stdio.h>
void menu()
{
printf("****************************\n");
printf("***** 1.add 2.sub ********\n");
printf("***** 3.mul 4.div ********\n");
printf("***** 0.exit********\n");
printf("****************************\n");
}
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 calc(int(*pf)(int, int))
{
int x, y, ret;
printf("请输入两个操作数>");
scanf("%d %d", &x, &y);
ret = pf(x, y);
printf("%d\n", ret);
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
int ret = 0;
do
{
menu();
printf("请选择\n");
scanf("%d", &input);
switch (input)
{
case 1:
calc(Add);
break;
case 2:
calc(Sub);
break;
case 3:
calc(Mul);
break;
case 4:
calc(Div);
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);
return 0;
}
我们通过回调函数来调用函数的地址,这样可以实现一个简易的计算器,但是当代码量大大增加,需要很多的函数的时候,这时用switch语句会显得比较冗余,那么我们可以借助函数指针数组来更简易实现这个功能。
②.函数指针数组
在之前曾描述过指针数组存放指针,那么显而易见,函数指针数组存放的是函数指针。那么,我们可以借助这个知识,重新写一下这个代码。
#include<stdio.h>
void menu()
{
printf("****************************\n");
printf("***** 1.add 2.sub ********\n");
printf("***** 3.mul 4.div ********\n");
printf("***** 0.exit********\n");
printf("****************************\n");
}
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 input = 0;
int x = 0;
int y = 0;
int ret = 0;
int(*pfArr[5])(int, int) = { 0,Add,Sub,Mul,Div };
do
{
menu();
printf("请选择\n");
scanf("%d", &input);
if (input == 0)
printf("退出计算器\n");
else if (input >= 1 && input <= 4)
{
printf("请输入两个操作数:>");
scanf("%d %d", &x, &y);
ret = pfArr[input](x, y);
printf("%d\n", ret);
}
} while (input);//代码大大简化
return 0;
}
这样代码可读性和去冗余都做得很好。
③回调函数
回调函数就是一个通过函数指针调用的函数,如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这时回调函数。
之前我们在写一个计算器的时候曾经用过回调函数,这里就不多赘述了。