此文时作者初次学习的时候写的笔记博文, 如有错误请批评指正,!!!!!!
上个文章我们讲到数组指针和指针数组 我们简单回顾一下:
int arr0[10];
//整型数组 ,每个元素是int类型的 有10个元素
int* arr1[10];
//指针数组, 也是一个数组 每个元素是int* 类型的,有10个元素
int(*p1)[10];
//这个是数组指针的定义, p1是一个指向数组的指针,
//这个数组有10个元素,每个元素是int类型的
int(*p2[10])[5];
//p2先和【】结合,所以p2是一个数组,这个数组有10个元素,
//每个元素的类型是int(*)[5],其实这里的元素就是数组指针,
//所以这个就是存放 数组指针的数组。
//所以这就是一个数组。
对于数组来说去掉数组名称和[ ] . 就得到数组每个元素的类型。上面的int (*p2[10])[5];
也是一样的p2先和[10]结合成为一个数组,去掉数组名和[10]就得到每个元素的类型
这个类型就是int(*)[5]。所以得到上面的是一个数组,是一个存放数组指针的数组。
数组指针一定要写出它指向数组的元素个数;int(*p1)[10]; 后面的【10】 不能省略。
4.数组传参和指针传参
在写代码的时候难免要把(数组)或者(指针)传给函数,那么函数的参数应该怎么设计呢?
4,1 一维数组的传参
一维数组:在传参数的时候传过去的是数组首元素的地址。
void test(int arr[10]);//这里的10可以省略。//这里的10是没有意义的。
void test(int* str);//这里的str就是数组首元素的地址。形参可以写成指针形式(一级指针)。
//这个指针指向的是数组首元素的地址,这个首元素是int类型的。
//所以一维数组在函数传参的时候本质上传递的是数组首元素的地址。
void test(int* arr2[20]);//20也是可以省略的,就算写成200也是没有意义的,
//因为本质上并不会去创建一个数组。传过来的只是数组首元素的地址。
void test(int** str);//这个的str就是指针数组首元素的地址。
//形参可以写成指针形式(二级指针)。
//所以str指向的是指针数组首元素的地址,这个首元素是int* 类型的。
int main()
{
int arr1[10] = { 0 };
int* arr2[20] = { 0 };
test(arr1);
test(arr2);
return 0;
}
4,2 二维数组的传参
二维数组:在传参数的时候传过去的是数组首元素的地址。但是二维数组的首元素是第一行,所以它在函数传参的时候传过去的是第一行的地址,(类似于传过去了一个一维数组)所以在接收的时候他会用到数组指针来接收二维数组传过来的参数。
void test(int(*str)[5]);
其实二维数组可以理解位几个相同大小的一位数组在你内存中连续存放的。
void test(int arr[3][5]);
void test(int arr[][5]);//这个写法肯定可以,而且前面[]可以省略,但是后面[]不能省略。
//也就是形式参数的行数可以省略但是列数不能省略。这里的3可写可不写。
//数组名本质上是数组首元素的地址(但是有两个例外)。对于二维数组来说首元素就是第一行。
//本质上二维数组在传参的时候传过来的还是数组首元素的地址。
//(这个首元素指的是第一行的元素(类似于一维数组))
void test(int (*str)[5]);//接收第一行的地址,就类似一个用一个一维数组的数组指针来接受。
//这里的str是指向二维数组的第一行的,第一行有[5]个元素,每个元素类型是int类型的。
//str+1指向的是第二行,str+2指向的是第三行。
//二维数在内存中是连续存放的,那个[5]是不能省略的。
//[5] 决定了它每一行的元素个数 也决定了str+1 它跳过了多少字节。
int main()
{
int arr[3][5] = { 0 };
test(arr);
return 0;
}
4,3 一级指针传参
一级指针传参我们应该用一级指针来接收。
void test(int* ptr, int sz);
//用一级指针接收。ptr和p里面存放的值是相同的。
//但是ptr是形参,而p是实参。共同指向一个内存空间。
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9 };
int* p = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
//一级指针p,传给函数
test(p, sz);
//其实这里用p和arr是相同的,都是首元素的地址,都传过去的是地址.
//都可以用一级指针来接收。
return 0;
}
void test(int * ptr); 这里test();函数可以传什么?
一级指针就可以:(可以是数组也可以是变量)
1,一个int类型的变量。
2,int类型的数组首元素的地址。或者数组名。
4,4 二级指针传参
二级指针传参 我们应该用二级指针来接收。
传参的时候类型匹配就可以了。
void test(char** ppc);
//二级指针接收
int main()
{
char a = 'w';
char* pa = &a;
char** ppa = &pa;
//这里的ppa就是二级指针。
//二级指针传参
test(ppa);
return 0;
}
void test(char** ppc); 这里test();可以传什么过去。
1,一个char类型的变量的二级指针。
2,一个指针数组。
void test(char** ppc); int main() { char ch = 'q'; char* pa = &ch; char** ppa = &pa; test(&pa); test(ppa); char* arr[5];//指针数组。 //他的首元素的地址 //srr==&arr[0] 又因为每个元素的类型是char* //arr的类型为 char** p //所以用上面可以接收它。 test(arr); char arr[3][5]; return 0; }
5,函数指针
函数指针就是指向函数的指针。
5,1 定义一个函数指针:
int Add(int a, int b)
{
return a + b;
}
int main()
{
int arr[10];
int(*p)[10] = &arr;
//p是数组指针变量。
printf("%p", &Add); //00007FF7C59E13C5
printf("%p", Add); //00007FF7C59E13C5
//这俩都是函数的地址,虽然写法不一样但是意义一样。(和数组不同)
int (*pf)(int, int) = Add;//这里的pf就是函数指针变量。
//第一个int 表示返回int
//后面的两个int 表示这个函数的参数是两个int类型的数据。没有就不写。
//但是括号不能省去。
//两个int 用()括起来说明它是函数,也就是pf指向函数。
//* 表示pf是一个指针。
//pf是函数的指针变量。
return 0;
}
5,2 函数指针怎么用
int Add(int a, int b)
{
return a + b;
}
int main()
{
int (*pf)(int, int) = Add;
//当然这里去掉pf 也就得到函数指针类型。
//int (*)(int, int)
int ret = (*pf)(2, 3);//注意这里的(*pf)的()一定不能省去。
int ret = pf(2, 3);
int ret = Add(2, 3);
//其实没有那个*都一样 ,所以这个*是可以省略的。
//这里这个*是没有意义的。
printf("%d", ret);//5
return 0;
}
练习:
int main()
{
//代码1
(*(void (*)())0)();
//void (*)() 这个是一个函数指针类型,
//(void (*)())这个就是对0进行强制类型转换,
//*(void (*)())0 这个是对零地址进行解引用
//所以这个代码首先把0 进行强制类型转换成一个函数指针类型,
//这就意味着0地址处放置了一个返回类型是void,参数是无参的一个函数。
//然后去调用0地址处的一个函数。
//这里前面那个*是可以省略的前面讲过。
int Add(int, int);
//这是函数的声明
//代码2
void (*signal(int, void(*)(int)))(int);
//这里可以看到signal()这是一个函数, 括号里面的是参数,
//第一个参数为int 类型的, 第二个参数是void(*)(int)函数指针类型的。
//signal(int, void(*)(int)) 这个就是函数声明中除了返回类型以外的东西了
//去掉上面的得到void (*)(int);函数指针类型,
//这个时候就可以改写为
//void (*)(int) signal(int, void(*)(int));
// 返回类型 函数名 int是参数1, void(*)(int)也是参数类型。
//这样是不是更容易理解 但是实际上写法是不能这样写的。这个写法是错误的。
//
//所以这个代码就是一个函数的声明,
//有返回void(*)(int),有函数名signal, 有参数 int和void(*)(int)。
//这个是可以改写的
typedef void(*pf_t)(int);//这是给函数指针类型void (*)(int)重新起一个名字叫:pf_t
//注意这里的pf_t 必须要放到(* )里面去,这是语法规定。
pf_t signal(int, pf_t);
//改写完成后就可以写成这样了。简化了 也容易理解了
return 0;
}
6. 函数指针数组
int main()
{
//指针数组
//字符指针数组
char* arr[5];
//整型指针数组
int* arr[5];
//函数指针数组
int (*pf1)(int, int) = add;
int (*pf2)(int, int) = sub;
int (*pf3)(int, int) = mul;
int (*pf4)(int, int) = div;
//以上是四个函数的函数指针变量。
int (*pf[4])(int, int) = { add, sub, mul, div };
//int (*pf[4])(int, int) = { pf1, pf2, pf3, pf4 };//这样也是可以的
//这个就是函数指针数组。
//这个和普通数组创建是一样的
for (int i = 0; i < 4; i++)
{
int ret = pf[i](8, 2);
printf("%d\n", ret);
//10, 6, 16, 4
}
return 0;
}
举例:实现一个简易的计算器
普通实现(代码比较冗余)
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = add(x, y);
printf("ret = %d\n", ret);
break;
case 2:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = sub(x, y);
printf("ret = %d\n", ret);
break;
case 3:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = mul(x, y);
printf("ret = %d\n", ret);
break;
case 4:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = div(x, y);
printf("ret = %d\n", ret);
break;
case 0:
printf("退出程序\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
使用函数指针数组的实现
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
while (input)
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
if ((input <= 4 && input >= 1))
{
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = (*p[input])(x, y);
}
else
printf("输入有误\n");
printf("ret = %d\n", ret);
}
return 0;
}
这个时候函数指针就可以简化代码了,但是也有局限,这个代码的函数参数和返回类型都是相同的才行。
拓展一下:
一个二维数组, int arr[[3][5];
test(arr)和test(&arr)。 在传参的时候,分别用什么接收。
但是test(&arr);这个是不常见的,也是不常用的。也非常容易混淆。
//test(arr) void test(int (*p)[5]); //传过来的是第一行的地址 //test(&arr) void test(int (*pp)[3][5]); //这个传过来的是整个二维数组的地址。 //pp是指向二维数组的指针 ,这个数组为三行 五列。