回顾:
数组名是首元素的地址——除以下两种情况外:
1.sizeof(数组名)
2.&数组名
这两种情况下数组名代表的是数组整体
指针是地址,指针变量是存放地址的变量,而指针变量经常简称为"指针"
数组下标与指针的关系
p[i] == *(p+i)
parr[i][j] == *(parr[i]+j) == *(*(parr+i)+j)
指针数组变量与数组指针变量
数组指针变量的定义格式
元素类型 (*数组指针名) [元素数量]
数组指针的类型格式
把“数组指针名”去掉就是其类型:
元素类型 (*) [元素数量]
例子
已知[]
的结合优先级比*
更高
int arr[5];//整形数组
int* parr1[10];//parr1是一个数组,有10个int*类型(即整型指针类型)的元素.即parr1是指针数组变量
int(*parr2)[10];//parr1是一个指针,该指针指向的数组有10个int类型的元素.即parr2是数组指针变量
int(*parr3[10])[5];//parr3是一个数组,有10个数组指针元素,每个数组指针指向一个有5个int类型元素的数组
一维数组传参
本质上传的是首元素地址
形参数组元素个数无关紧要,可以省略
#include <stdio.h>
void test1(int arr[]){}//数组形式的形参
void test2(int arr[10]){}
void test3(int arr[100]){}//这样虽然语法正确,但乱填会使人误会
void test4(int* arr){}//指针形式的形参
void test5(int *arr[20]){}
void test6(int *arr[]){}
void test7(int **arr){}//arr2是int*类型元素的指针数组
int main(){
int arr[10]={0};
int* arr2[20]={0};
test1(arr);
test2(arr);
test3(arr);
test4(arr);
test5(arr2);
test6(arr2);
test7(arr2);
return 0;
}
二维数组传参
函数形参只能省略第一个[]的数字。因为对于一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
二维数组看作以一维数组为元素的一维数组。
因此二维数组的“首元素地址”,首元素指的是第一行。
所以“首元素的地址”存放在一个指向一维数组的指针中
二维数组与二级指针没有必然联系,传一级指针变量的地址才可以用二级指针,但二维数组传的是一维数组的地址
#include <stdio.h>
void test1(int arr[3][5]){}
void test2(int arr[][5]){}
void test3(int(*p)[5]){}//指向首元素----二维数组的第一行(一个一维数组)的指针
int main(){
int arr[3][5] = { 0 };
test1(arr);
test2(arr);
test3(arr);
return 0;
}
一级指针传参
#include <stdio.h>
void print(int *p, int sz){
int i = 0;
for (i = 0; i < sz; i++){
printf("%d\n", *(p + i));//p[i] == *(p+i)
}
}
int main(){
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int *p = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
//一级指针p,传给函数
print(p, sz);
return 0;
}
已知一个函数的参数为一级指针,可以传什么样的参数给这个函数?
#include <stdio.h>
void test(int *p){}
int main(){
int a = 10;
int* p1 = &a;
int arr[10] = { 0 };
test(&a);//传普通整型变量地址
test(p1);//传一级指针
test(arr);//传数组名
return 0;
}
二级指针传参
#include <stdio.h>
void test(int **ppa){}
int main(){
int a = 10;
int* pa = &a;
int** ppa= &pa;
int* arr[5];
test(ppa);//传二级指针变量
test(&pa);//传一级指针变量的地址
test(arr);//传一级指针数组的数组名,也是传一级指针变量的地址
return 0;
}
函数指针变量
#include <stdio.h>
void test(){
printf("hehe\n");
}
int main(){
printf("%p\n", test);
printf("%p\n", &test);
return 0;
}
输出结果:
“数组名”和“&数组名”值一样但意义不同,而“函数名”与“&函数名”的值和意义相同
函数指针变量的格式
函数的返回类型 (*函数指针名) (第一个参数类型, ... ,第n个参数类型)
函数指针的类型
把“函数指针名”去掉就是其类型:
函数的返回类型 (*) (第一个参数类型, ... ,第n个参数类型)
使用函数指针调用函数
#include <stdio.h>
int Add(int x, int y) {
return x + y;
}
int main() {
int (*pf)(int, int) = &Add;//pf是函数指针变量
int (*pf)(int, int) = Add;//效果相同,因为“函数名”与“&函数名”的值和意义相同
int ret = Add(2, 3);//ret == 5//直接调用函数
ret = (*pf)(4, 5);//ret == 9//对pf解引用并传参,效果相同
ret = pf(4, 5);//因为含义与值都相同,不解引用也一样
ret = (****pf)(4, 5);//多来几个*也没区别,*只是摆设
return 0;
}
函数指针用*解引用的意义主要在于从语法层面更方便初学者理解
函数指针例
以下两例取自《C陷阱和缺陷》一书
(*(void(*)())0)();
//void(*)() 为无参数且不返回的函数指针的类型,
//,然后将0这个原本为int类型的值进行强制类型转换,
//,转换为void(*)() ,也就是将0当作函数地址。
//之后使用*解引用这个函数指针,调用地址为0的函数,
//由于该函数无参,所以最后的()内无参。
//综上,该代码表示了一次函数调用
void (* signal(int,void(*)(int)))(int);
//signal是函数名
//signal函数有两个参数,第一个是int类型,
//,第二个是void(*)(int)的函数指针类型
//signal函数的返回类型还是void(*)(int)的函数指针类型
//综上,该代码是一次函数声明
//上面的代码可以简化:
typedef void(*pf)(int);//将pf作为void(*)(int)类型的别名
//注意,不能写成typedef void(*)(int) pf; 这是错误写法
pf signal(int, pf);
函数指针数组
存放函数指针的数组
函数指针数组的格式
函数的返回类型 (*函数指针数组名[元素个数]) (第一个参数类型, ... ,第n个参数类型)
函数指针数组的用途例:转移表(简易计算器)
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int Add(int x, int y) {
return x + y;
}//类型为 int (*) (int, int)
int Sub(int x, int y) {
return x - y;
}//类型同为 int (*) (int, int)
int Mul(int x, int y) {
return x * y;
}//类型同为 int (*) (int, int)
int Div(int x, int y) {
return x / y;
}//类型同为 int (*) (int, int)
int main() {
int x, y;
int input = 1;
int ret = 0;
int (*p[5])(int, int) ={0,Add,Sub,Mul,Div};//转移表-参阅《C和指针》
while (input) {
printf("0.exit 1.add 2.sub 3.mul 4.div\n");
printf("输入选项数字\n");
scanf("%d", &input);
if (input == 0) {
printf("退出计算器...\n");
return 0;
}
if ((input <= 4 && input >= 1)){
printf("输入两个整型操作数,用空格隔开:");
scanf("%d %d", &x, &y);
ret = (*p[input])(x, y);
}
else
printf("输入有误\n");
printf("ret = %d\n", ret);
printf("\n");
}
return 0;
}
指向函数指针数组的指针
指向函数指针数组的指针是一个指针,该指针指向一个数组 ,该数组的元素都是函数指针:
void test(const char* str){
printf("%s\n", str);
}
int main(){
//函数指针pf
void (*pf)(const char*) = test;
//函数指针的数组pfArr
void (*pfArr[5])(const char* str);
pfArr[0] = test;
//指向函数指针数组pfArr的指针ppfArr
void (*(*ppfArr)[10])(const char*) = &pfArr;
return 0;
}
回调函数-依赖函数指针实现
-回调函数是通过函数指针调用的函数。
-若将函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,就称之为回调函数。
-回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
回调函数例
不使用转移表实现前文中的计算器:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
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 = 0;
int y = 0;
int ret = 0;
printf("输入两个整型操作数,用空格隔开:");
scanf("%d %d", &x, &y);
ret = pf(x, y);//由Calc函数根据所传地址调用计算用的函数,而不是单独写出各计算函数的调用
printf("ret = %d\n", ret);
}
int main(){
int input = 0;
do{
printf("0.exit 1.add 2.sub 3.mul 4.div\n");
printf("输入选项数字\n");
scanf("%d", &input);
switch (input){
case 1:
Calc(Add);//Calc是Add函数的调用方
break;
case 2:
Calc(Sub);
break;
case 3:
Calc(Mul);
break;
case 4:
Calc(Div);
break;
case 0:
printf("退出计算器...\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}