文章目录
回顾指针
1,指针就是个变量,用来存放地址,地址 唯一标识 一块 内存空间。
2.指针的大小是固定的 4 / 8字节(32位平台 / 64位平台)。
3.指针是有类型的 ,指针的类型决定了指针的 +- 整数的步长,指针解引用操作的时候的权限(一次访问几个字节内容)
4.指针的运算
举个例子回忆一下
#include<stdio.h>
void test(int* arr)// 这里的 arr 是 数组首元素地址的一份拷贝
{
printf("%d\n",sizeof(arr[0]));// 计算的数组的一个元素大小,输出为 4
printf("%d\n",sizeof(arr));// 计算的是地址的大小(这里不能算是数组的地址,因为 该地址是test传过来的 首元素地址 的一份拷贝),输出为 4
// 32位系统
sizeof(arr)在这里是求指针(地址)大小(4 byte)
sizeof(arr[0])在这里求的是一个元素的大小,int类型(4字节)
如果是64位系统。指针大小为 8 字节,输出此时就为 2
}
int main()
{
int arr[10] = { 0 };
test(arr);//传过去的是数组首元素地址,因为不是单独 与 sizeof 和 & 操作符连用
return 0;
}
1.字符指针
在指针的类型 中 我们知道有一种 指针类型 为 字符指针 char*·
程序一:
#include<stdio.h>
int main()
{
char ch = 'w';
char* pc = &ch;
*pc =='w';
return 0;
}
程序二:
#include<stdio.h>
int main()
{
char arr[] = "abcdef";
char* pc = arr;//这里存入的是首元素 a 的地址
printf("%s\n",arr);//abcdef
printf("%s\n",pc);//abcdef 两者都是向后打印,直到遇到'\0'停止
%s 就是 根据 给的地址位置开始向后打印的,知道遇到'\0'停止
return 0;
}
程序三:
#include<stdio.h>
int main()
{
char* p = "abcdef";// "abcdef" 双引号引起来的abcdef\0,是一个常量字符串
// 上表达式的意思是,把 a 的地址 存入指针变量p里面去
printf("%c\n",*p);// *p == a
// 输出为 a
printf("%s\n",p);//从存入p的这个地址(首元素a的地址)开始往后打印知道遇到 '\0'
// 即输出为 abcdef
return 0;
}
程序四:
#include<stdio.h>
int main()
{
const char* p = "abcdef";// 最稳妥写法,就是在 * 前面加上 const
*p = 'w';// 这时候你想改都改不了,况且 "abcdef" 是一个常量字符串,也改不了
printf("%s\n", p); //你会发现 没有任何输出,程序崩溃。违规操作
//还有一个原因 abcdef\0 是一个常量字符串,是不可以被改变的(const:是变量具有常量属性)
return 0;
}
程序五:
#include<stdio.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcdef";
if (arr1 == arr2)//这里是两个不同数组的数组名(不同的首元素地址),是否相同
//肯定不同,两个不同数组的起始空间肯定不一样
{
printf("hehe\n");
}
else
{
printf("haha\n");// 所以输出这条语句
}
return 0;
}
程序六:
#include<stdio.h>
int main()
{
建议下面两句表达式在 * 前面防疫 const ,这样就更保险,无法通过解引用去修改常量字符串的内容
这样写,虽然意义不大(常量字符串本身并不能被修改),语法更为准确
const char* p1 = "abcdef";
const char* p2 = "abcdef";
这里把常量字符串 abcdef\0 的首元素(a)地址分别存入 2 个指针变量
是因为abcdef\0是常量字符串,不可改变,因此没有必要创建2个,直接共用一个,即 p1 == p2
if (p1 == p2)
{
printf("hehe\n");// 所以输出这条语句
}
else
{
printf("haha\n");
}
return 0;
}
指针数组 - 本质上是一个数组
在pointer文章中,我们也学了指针数组,指针数组 指的是一个 存放指针 的数组
程序一:
#include<stdio.h>
int main()
{
int arr[10] = { 0 };// 整形数组
char ch[5] = { 0 };// 字符数组
int* parr[4];// 这就是一个存放 整形指针 的数组,简称 指针数组
char* pch[5];// 这就是一个存放 字符指针 的数组,简称 指针数组
return 0;
}
程序二:
#include<stdio.h>
int main()
{
int a = 10;
int b = 20;
int c = 30;
int d = 40;
int* arr[4] = { &a, &b, &c, &d };
等价于 //int* pa = &a;
//int* pb = &b;
//int* pc = &c;
//int* pd = &d;
int i = 0;
for (i = 0; i < 4; i++)
{
printf("%d ",*arr[i]);// 10 20 30 40
}
return 0; // 指针数组很少怎么用 ,下面程序将告诉你,指针数组怎么使用
}
程序三:
#include<stdio.h>
int main()
{
int arr1[] = { 1, 2, 3, 4, 5 };
int arr2[] = { 2, 3, 4, 5, 6 };
int arr3[] = { 3, 4, 5, 6, 7 };//以上三个表达式,就是在说我有三数组,内容是。。。。
int* parr[] = { arr1, arr2, arr3 };
// 把上面 三个数组 的 数组名/数组首元素地址 存入这个 指针数组
int i = 0;
for (i = 0; i < 3; i++)// 遍历 指针数组 parr 的元素
{
int j = 0;
for (j = 0; j < 5; j++)// 遍历 指针数组 parr 的 元素 所指向的 数组 的 元素
{
printf("%d ",*(parr[i] + j));
}
printf("\n");
}
return 0;
}
这里我们回顾一下 二级指针
int* arr1[]; //一级整形指针数组.
int** arr[];// 二级整形指针数组
ichar* arr2[]; //一级字符指针数组
char** arr2[]; //二级字符指针数组
数组指针 - 指针
程序一:
#include<stdio.h>
int main()
{
int* p = NULL;// 整形指针 - 指向 整形 的指针 作用:可以 存放 整形的地址
char* pc = NULL;//字符指针 - 指向 字符 的指针 作用:可以 存放 字符的地址
数组指针 - 指向 数组 的指针 作用:可以 存放 数组的地址
//int arr[10] = { 0 }; 整形数组
// arr - 首元素地址
// &arr[0] - 首元素的地址
// &arr - 数组的地址
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int(* p)[10] = &arr;// 把数组的地址存起来
// 为什么不能去掉(),因为 [] 的优先级 比 * 高,去掉(),p就和数组[10]先结合,就成 指针数组(存放指针的数组) 了
// 加 () 把 * 和 p 先结合,使 p 成为一个指针 (另外请注意 这里不是解引用,只是说明 p 是一个指针)
// 把 *p 去掉,还剩int [10],意思就是说该指针(p)指向 一个 元素个数为 10 的数组,且数组每个元素的类型为整形
// 即 上表达式 int(*p)[10] 就是数组指针
return 0;
}
程序二:
#include<stdio.h>
int main()
{
char* arr[5];
//如何把上表达式的数组存入 数组指针?
如下
char*(*pa)[5]=&arr;
// 先用()把 * 和 pa(指针变量名) 结合起来,使 pa 为一个指针,也就是说 * 告诉我们 pa 是个指针
//再在后面加[5],意思是 指针 指向一个元素个数为5的数组
// 又因为 指针 指向的数组 的 元素类型 为 char*,所以在前面补上
int arr2[10] = { 0 };
int (*pa2)[10] = &arr2;
return 0;
}
&数组名 VS 数组名
数组名 绝大部分时候 都是为 首元素地址
只有两种情况例外 &数组名 和 sizeof(数组名)
在这两种情况下的数组名,代表是整个数组,取出的是数组的地址(与数组首元素地址相同,但意义不同)
举个例子
&arr+1 - 直接 跳过一整个 数组的字节
比如 int arr[10],他的大小是40个字节,&arr+1 数组地址会加上40
如果是 arr+1 它就只跳过一个元素,意思就是 跳过第一个元素,地址指向第二个元素
数组指针的用法 : 一般用在 二维数组
程序一:
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10};
int (*pa)[10] = &arr;
int i = 0;
for (i = 0; i < 10; i++)
{
//printf("%d ",(*pa)[i]);// *pa 就相当于数组名 *pa == arr
printf("%d ",* (*pa+i) );//等价于 printf("%d ",*(arr+i))
}
return 0; // 但 数组指针 不是这么用的,以上只是让你对它理解更深一点
}
在 明白 数组指针 的真正用途之前,我们需要观察一个程序
#include<stdio.h>
int main()
{
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int i = 0;
int* p = arr;// 指针变量p 接收的是 数组的首元素地址,即 p == arr
for (i = 0; i < 10;i++)
{
printf("%d ",*(p+i));// 通过数组首元素的地址,来遍历数组元素
}
printf("\n");
上下两个循环表达大额效果是相同的
for (i = 0; i < 10; i++)
{
printf("%d ", *(arr + i));
}
printf("\n");
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);// arr[i] == *(arr+i) == *(p+i) == p[i]
}
printf("\n");
上下两个循环表达大额效果是相同的
for (i = 0; i < 10; i++)
{
printf("%d ", p[i]);// arr[i] == p[i]
}
//以上所有写法都是等价的。
return 0;
}
程序二(数组指针的用法):
#include<stdio.h>
void print1(int arr[3][5], int x, int y)
{
int i = 0;
int j = 0;
for (i = 0; i < x; i++)// 双重循环遍历二维数组
{
for (j = 0; j < y; j++)
{
printf("%d ",arr[i][j]);// printf("%d ". *(*(arr+i)+j) );
}
printf("\n");
}
}
void print2(int(*p)[5], int x, int y)//由于 二维数组转过的是 首元素地址(一维数组的地址),需要一个一维数组指针来接收
{
int i = 0;
int j = 0;
for (i = 0; i < x; i++)// t通过双重循环,来遍历二维数组
{
for (j = 0; j < y; j++)
{
//printf("%d ", *(*(p+i)+j));// p 是 一维数组的地址 (一行),*p 就是找到了第一行的数据,也就是一维数组的数组名
// *( p + i),i=0,还是第一行,i=1 是第二行, i=2 第三行
// *(p+i)+j 就是第几行 第几个元素
printf("%d ",p[i][j]);
// *(*(p+i)+j)== p[i][j] 先用一个括号将 p 和 i括起来,得到一维数组的首元素地址,在对其解引用得到"数组名",
再用一个括号将其和 j 括起来,得到 i 行 第 j 个元素的地址,在对其 解引用,得到该元素的值,并将其打印(注意! [] 比 * 的优先级高)
//printf("%d ", *(p[i]+j))
// *(p+i) == arr[i] == p[i]
// *(p[i]+j) == p[i][j]
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { { 1, 2, 3, 4, 5 }, { 2, 3, 4, 5, 6 }, {3, 4, 5, 6, 7} };
print1(arr,3,5);
这里的 数组名 就是 首元素【{1,2, 3,4, 5}】地址
首先我们要 把二维数组 看作 一维数组,把 第一行{1,2,3,4,5}数据,也就是我们的第一个元素(首元素),看作一个一维数组 int a[5]
//以此类推 第二行数据 就是 第二个元素(也是一个一维数组), 第三行数据 就是 第三个元素(也是一个一维数组)
print2(arr,3,5);// 那么 传过去的是数组首元素地址,而且是一个一维数组的地址
return 0;
}
小汇总
int arr[10] //arr是一个整形数组,具有10个元素.
int* parr1[10];//parr1是指针数组,首先它是一个数组,具有10个元素,且每个元素的类型是(int*) ,
int(*parr2)[10]; parr2是一个整形数组指针
用括号让 * 与 parr2 先结合
所以parr2是一个指针,该指针指向了一个数组,数组有10个元素,每个元素的类型是int
int( * parr3 [10] ) [5]
因为()的优先级最高,所以先判断()里的内容
又因为 * 和[],[]的优先级更高,所以 parr3先与[]结合,所以parr3是个数组
把 parr3[10]去掉,还剩下int(* )[5] ,就是它的元素类型.
例子: int arr[10];去掉arr[10],乘下的 int,就是它的类型(整形)
那int(* )[5]是个什么类型?
仔细观察一下,你会发现,它和数组指针 int(*p)[10] 一样
.那么、我们可以说 parr3是个数组,元素有10个,每个元素都是一个整形数组指针,这个指针能指向5个元素,每个元素的类型是int.的整形数组。(我们称 int( * )[5] 为 整形数组指针类型,简称 数组指针类型 )
数组参数、指针参数
在写代码的时候难免要把 [ 数组 ] 或者 [ 指针 ]传给函数,那函数的参数该如何设计?
一维数组传参
#include<stdio.h>
void test(int arr[])// OK
{}
void test(int arr[10])// OK
{}
void test(int* arr)// OK
{}
void test2(int* *arr)// OK
{}
void test2(int* arr[20])// OK 这里 20 跟上表达式一样可以省略
{}
int main()
{
int arr[10] = { 0 };
int* arr2[20] = { 0 };
test(arr);
test2(arr2);
return 0;
}
二维数组传参
#include<stdio.h>
void test(int arr[3][5]) // OK
{}
void test(int arr[][5]) // OK 维数组 的 行 是能省略的
{}
void test(int arr[3][]) // NO 二维数组 的 列是不能省略的
{}
void test(int arr[][]) // NO 二维数组 的 列是不能省略的
{}
void test(int* arr) // NO 这只能用来接收一维数组的元素地址(数组的地址,要用数组指针来接收,而不是数组一个一级指针),不能接收二维数组的传参
{}
void test(int* *arr) // 二维数组的数组名 是 首元素地址(第一行的地址,可以将其当做一个一维数组的地址】)
{} // 一个数组的地址,是不 能放进 二级指针的
// 二级指针 是用来 存放 一级指针变量 的地址
//所以该写法也是错的 NO
void test(int (*arr)[5]) // ok
()使 * 和 arr 先结合,使 arr 成为指针,用它来接收二维数组的地址(首元素地址)
将二维数组的首元素地址 就第一行数据的地址(可以看作 一维数组的地址 ,所以需要一个 数组指针 来接收),
该指针指向一个有五个元素的数组(二维数组的一行数据),且每个元素的类型为 int
{}
int main()
{
int arr[3][5] = { 0 };
test(arr);// 二维数组 的 数组名 是 首元素地址(第一行的地址数据的地址,将其当做 一个 一维数组的数组名)
}
一级指针传参
#include<stdio.h>
void print(int* p,int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ",*(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;
}
思考: 当 一个函数 的 参数部分 为 一级指针的时候,函数能接受什么参数?
#include<stdio.h>
void tset(int* p)// 这里 的一级指针变量 p 是 print 函数 中 一级指针变量 的 一份拷贝
{
;
}
void print(int* p,int sz)
{
int a = 10;
int* p = &a;
test(&a);// ok
test(p);// ok,这里传的是 一级指针变量本身,而不是 一级指针 的 地址,所以不需要 二级指针来接收
return 0;
}
能接受 一级指针变量本省 和 一个整形数据的地址
二级指针的传参
程序一(传二级指针变量本身和一级指针变量地址):
#include<stdio.h>
void test(int* *ptr)
{
printf("num = %d\n",**ptr);
}
int main()
{
int n = 10;
int* p = &n;
int* *pp = &p;// 最右边的*,说明pp是一个指针,该指针指向 p;左边 int*是类型,是该指针指向 p 的类型
test(pp);// ok,这里传的是 二级指针变量本省,所以接收只需要 一个 二级指针,相当于 将其拷贝一份数据
test(&p);// ok 这里传的是 一级指针变量 的 地址,一个一级指针 的 地址,需要一个二级指针来接收
return 0;
}
程序二(传 一级指针变量的地址):
#include<stdio.h>
void test(int* * p)
{
printf("hehe");
}
int main()
{
int* ptr = 0;
test(&ptr);// 这里就是典型的,传址(传一级指针变量的地址),需要一个二级指针来接收
return 0;
}
程序三(传 二级指针变量的本身):
#include<stdio.h>
void test(int* * p)
// 因为 test函数传的是二级指针变量的本身,而不是二级指针变量的地址,所以我们只需用一个相同类型的二级指针来接收test的传参
// 这里 的 二级指针变量 p, 相当于 是 二级指针变量 pp 的一份临时拷贝
{
printf("hehe");
}
int main()
{
int* ptr = 0;
int* *pp = &ptr;
test(pp);// 这里传的是 二级指针变量本身
return 0;
}
程序四(传 指针数组 的 数组名):
#include<stdio.h>
void test(int* * p)
{
printf("hehe");
}
int main()
{
int* arr[10];
test(arr);
//指针数组 也可以,这里传的是数组名(首元素地址),因为 指针数组里 存放的元素的类型是 int* ,也就是说 首元素地址 其实是一个 一级指针变量(元素)的地址,一个 一级指针变量的地址,需要一个 二级指针变量 来接收
return 0;
}
思考:当函数的参数为二进制的时候,可以接收什么参数?
一级 指针变量 的地址
二级指针变量的本身
存放 一级指针数组 的 数组名
函数指针
数组指针,是一个指向数组的指针
那么,函数指针,是一个指向函数的指针 ,一个存放 函数的地址 的指针。
程序一:
#include<stdio.h>
int add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 10;
int b = 20;
printf("%d\n", add(a, b));
printf("%p\n",&add);
printf("%p\n", add);// 这两句程序,输出的结果是一样的。
//因为 函数名 和 &函数名 都是函数的地址, 而 函数 只有一个,所以函数的地址是唯一的
return 0;
}
程序二:
#include<stdio.h>
int add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int arr[10] = { 0 };// 整形数组
int(*p)[10] = &arr;// 整形数组的地址,需要一个 整形数组指针来接收
int (*pa)(int,int) = add;
// 函数指针,先用()把 * 和 pa 先结合,是 pa 是一个指针(指针才能存放地址),
// 该指针 指向的函数 有两个整形参数,然后该函数类型是 int
// 只需要告诉函数的参数类型就行,x 和 y 写不写都无所谓
printf("%d\n",(*pa)(2, 3));// 5
(*pa) == add,就是说 add 的地址,存入 函数指针 pa 中,我们再通过 解引用,找到add 函数,并对其调用。
就 好像一个函数声明,告你说,这样也能调用 add 函数
return 0;
}
程序二:
#include<stdio.h>
void print(char* str)
{
printf("%s\n", str);
}
int main()
{
void(*p)(char*) = print;
//这里不是解引用,我们用(),先让 * 和 p 结合,使p为一个指针
//该指针 指向函数的参数的类型是 char*
//函数返回类型是 void
//这个时候 p 就是我们的所谓的函数指针
(*p)("hello word!");
等价于
print("hello word!")
return 0;
}
你可以这么认为 函数指针 就是 函数名
阅读两段有趣的代码
代码1
void (*)() - 函数指针类型
( void (*)() )0, 最外面的括号 是 强制类型转换符号,就是把 0 进行强制类型转换( int -> 函数指针类型 )
到那时候 0 就是一个函数的地址
( * ( void ( * ) ( ) ) 0)
*解引用,调用 0 地址处的该函数【void (*)(),假设该式子等价于 void(*p)(int,int),这样你们应该能理解】
就是调用 地址 0 处 的 函数 void(*p)(int,int)
代码2(简化 嵌套函数指针的写法)
void(*signa1( int, void(*)(int))) (int);
signal这是一个函数声明
signal 函数的参数有两个,第一个是 int ,第二个是函数指针,该函数指针指向的函数的参数类型是int,返回类型是void
signal 函数 的 返回类型 也是一个 函数指针,该函数指针指向的函数的参数类型是int,返回类型是void
然后我们把上面 三个去掉(函数 signal,和 它的两个参数)
剩下: void (*)(int) 这又是一个函数指针类型,是函数signa1(int, void(*)(int))的返回类型
typedef void(* pfun_t)(int); 现在这个时候 pfun_t 就是函数指针类型
// pfun_t 简写(重命名)是不能放后面的,要放在(*这里)
上下两者 对 函数名 重命名的写法是不相同的
typedef unsigned int uint; // 简写是可以直接写在后面的
简化 void(*signa1(int, void(*)(int)))(int);
typedef void(*pfun_t)(int);
pfun_t signa1(int, pfun_t);
函数指针实战
#include<stdio.h>
int add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int arr[10] = { 0 };
int(*p)[10] = &arr;
int (*pa)(int,int) = add;// 函数指针,先用()把 * 和 pa 先结合,是 pa 是一个指针(指针才能存放地址),指向函数的两个整形参数,然后该函数类型是 int
// 只需要告诉函数的参数类型就行,x 和 y 写不写都无所谓
printf("%d\n",(*pa)(2, 3));// 5
printf("%d\n", (**pa)(2, 3));// 5
printf("%d\n", (***pa)(2, 3));// 5
照理说 第一次 *pa 解引用,通过 pa的存入的地址,找到add 函数,第二次 **pa,以*pa为地址再找,以此类推。
但由 上三个表达式 的输出结果显示,* 没有起到应有的 作用
那么我们可不可以 去掉 * 呢?
printf("%d\n", pa(2, 3));// 5
结果证明 可以
原因在程序一中:
printf("%p\n",&add);
printf("%p\n", add);// 这两句程序,输出的结果是一样的。
//因为 函数名 和 &函数名 都是函数的地址, 而 函数 只有一个,所以函数的地址是唯一的
那么,再加上 pa本来就是等函数的地址(pa == &add == add),所以 在使用函数指针时,可以直接写 函数指针变量
有的时候会有些人会加上 * 的原因,这样可读性高,让读者 能明白 pa 其实是一个指针
但 加上了 * ,要注意加上(),要不然会出现问题,
例 *pa(2,3),这时pa会先于(2,3)结合,形成一次函数调用,然后再解引用,就是说 对 调用结果 5 进行解引用,不是我们想要的结果
return 0;
}
函数指针数组
#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;
}
int main()
{
int* arr[5];//指针数组 arr是一个数组,有5个元素,且每个元素的类型为int*,即每个元素都是一个整形指针、
int(*pa)(int ,int) = add;// 我们有4 个函数要被调用,要是 一个接着一个接着这样会麻烦
这时需要一个数组,这个数组可以存放4个函数的地址 - 函数指针的数组(前提是它们的参数和类型是完全一致的)
int(*parr[4])(int,int) = {add,sub,mul,div};// 函数指针数组
将上式拆成 2 部分
int (*) (int,int)
parr[4]
表达的意思是 parr 先和 [4] 结合,成为一个数组,元素有4个,每个元素 都是一个函数指针
int i = 0;
for(i = 0; i < 4; i++)
{
printf("%d ",parr[i](2, 3));// 加法 5, 减法 -1 乘法 6 除法 0
}
return 0;
}
练习(写一个函数指针 pf,能够指向 my_strcpy)
首先我们要知道 我们的自定义函数的类型: char* my_strcpy(char* dest, const char* src);
然后我们对它进行改造:char* ( * pf)(char * ,const char * ) = my_strcpy;
#include<stdio.h>
#include<assert.h>
char* My_strcopy(char* destination,const char* source)//这里本质上是个指针
{
assert(destination&&source);
char* ret = destination;
while (*destination++ = *source++);
return ret;
}
int main()
{
char arr[30] = { 0 };
char arr1[] = "abc";
char arr2[] = "def";
char arr3[] = "adcde";
char arr4[] = "abcdef";
char* arr5[4] = { arr1, arr2, arr3, arr4 };
char*(*pf)(char*, const char*) = My_strcopy;
for (int i = 0; i < 4; i++)
{
printf("%s\n", pf(arr,arr5[i]));
}
return 0;
}
写一个函数指针数组,能存放 4个 my_strcpy 函数的地址
char* ( * pfarr[4])(char*, const char*);
这个我就不写,就跟 指针数组一样,给个打括号放函数名就行了
接下来我们用函数指针数组 实现计算器
正常方式
#include<stdio.h>
void menu()
{
printf("**************************\n");
printf("** 1,add 2.sub ******\n");
printf("** 3,mul 4,div ******\n");
printf("***** 0.退出 ******\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;
do
{
menu();
printf("请选择");
scanf("%d",&input);
switch (input)
{
case 1:
printf("请输入两个操作数:");
scanf("%d%d", &x, &y);
printf("%d\n",add(x,y));
break;
case 2:
printf("请输入两个操作数:");
scanf("%d%d", &x, &y);
printf("%d\n",sub(x,y));
break;
case 3:
printf("请输入两个操作数:");
scanf("%d%d", &x, &y);
printf("%d\n",mul(x,y));
break;
case 4:
printf("请输入两个操作数:");
scanf("%d%d", &x, &y);
printf("%d\n",div(x,y));
break;
case 0:
printf("退出程序");
break;
default:
printf("输入错误,请重新选择:");
break;
}
} while (input);
return 0;
}
函数指针数组方式
#include<stdio.h>
void menu()
{
printf("**************************\n");
printf("** 1,add 2.sub ******\n");
printf("** 3,mul 4,div ******\n");
printf("***** 0.退出 ******\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(*parr[])(int, int) = { 0, add, sub, mul, div };// parr 是一个函数指针数组
// 函数指针数组的用途 又称 转移表
do
{
menu();
printf("请选择");
scanf("%d", &input);
if (input >= 1)
{
printf("请输入操作数:");
scanf("%d%d", &x, &y);
printf("%d\n", parr[input](x, y));
}
else if (input == 0)
{
printf("退出程序\n");
break;
}
else
{
printf("选择错误,请重新输入");
}
} while (input);
return 0;
}
指向 函数指针数组 的 指针
int arr[10];
int(*p)[10] = &arr; // p就是一个指向 整形数组 的指针
//这就是一个整形数组指针
int* (*p)[10] = &arr;// p 就是一个指向 整形指针数组 的 指针
int add(int x, int y)
{
return x + y;
}
int(*pf)(int,int) = add;//pf 是 函数指针
int(*pf[4])(int,int)// 这是一个 函数指针数组
int(*(*pf)[4])(int,int)// pf 就是一个 指向函数指针数组的 指针
()让 pf 和 * 先结合,使其成为一个指针,去掉他们还剩 int(*()[4])(int,int)很明显就是一个 函数指针数组
函数指针数组 与 函数指针数组的指针 (详解)
函数指针数组
/先写个数组pfarr[5]
pfarr[5];
// 再用()把 * 和数组 pfarr[5],结合 -指针数组
(*parr[5]);
// 再写函数参数类型与返回类型
int(*parr[5])(int, int);// pafarr 是一个 函数指针 的 数组
指向 函数指针数组 的 指针
*ppfarr = &pfarr;// 函数指针数组 的 地址,存起来,
//ppfarr就是 函数指针数组 的 指针
//再将其替换入内,记得加括号
即:
int(*(*ppfarr)[5])(int, int) = &pfarr;
// 首先用()把 pparr 先和 * 结合,即pparr是一个指针
//该指针向外一看,指向一个数组 [5], pparr 指针指向 一个数组
//去掉 数组 和 指针 : int(*)(int,int) 这是一个函数指针类型
//数组每个元素,都是 函数指针类型
// 所以 ppfarr 就是一个指向 函数指针 数组 的 指针
int(*p)(int, int); // 函数指针
int(*p[5])(int, int); // 函数指针数组
int(*(*p)[5])(int, int);// 函数指针数组 的 指针
函数指针数组的指针 - 首先它是指针,其次 它是一个数组,最后是 一个函数指针
回调函数
回调函数:
是一个通过函数指针调用的函数,如果你把函数的指针(地址)作为参数传递给另一个函数
当这个指针 被用来 调用 其所指向的函数时,我们就说这是回调函数,
回调函数不是由 该函数 的 实现方 直接调用,而是在 特定的事件 或 条件 发生时 由 另外的一方 调用 的
用于 对该 事件 或 条件 进行响应。
简单来说,就是通过 把 实现功能函数的地址,交给另一哥 函数 ,由它去调用我们的 功能函数,
不能直接调用 功能函数,(就是说,你想玩电脑,得经过父母的同意,不然你玩个屁。)
实例:
#include<stdio.h>
void menu()
{
printf("**************************\n");
printf("** 1,add 2.sub ******\n");
printf("** 3,mul 4,div ******\n");
printf("***** 0.退出 ******\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 x = 0;
int y = 0;
printf("请选择两个操作数:");
scanf("%d%d",&x,&y);
printf("%d\n", pf(x, y));
}
int main()
{
int input = 0;
do
{
menu();
printf("请选择");
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;
case 0:
printf("退出程序");
break;
default:
printf("输入错误,请重新选择:");
break;
}
} while (input);
return 0;
}
qsort 函数 的使用
先来回顾一下冒泡排序 :只能排整形数据
#include<stdio.h>
void bubble_sort(int*arr, int sz)
{
int i = 0;
for(i = 0; i < sz - 1; i++)// 整个数组,只用 排 元素的总个数 - 1,因为最后不需要排
// 就好比 432 1,前面的 比 1 大的数,都排到前面去了,
// 1 它自己就只能 stay here
{
//一趟冒泡排序
int j = 0;
for (j = 0; j < sz - 1 - i; j++)
//那 一个数 和 它屁股后面的数 进行比较
{
if (arr[j] > arr[j + 1])
// 如果 它 比 它屁股 大,它就前进一位,然后再跟它目前的屁股,再比一次,直到,他没屁股大,停止,然后屁股再去跟它后面的人去比
// 第一次要比 九次,
// 它一次比完之后,一开始 跟它比的 屁股的哪一位,他就要开始比了,
// 由于 屁股这位,跟它比过了,他就不会去比了,因为结果都知道,还比个锤子哟!
// 所以 屁股 这位 比的次数 比它 少一次,
// 而 屁股后面那位,跟 它们都比过了,它就比九次 少2次
// 以此类推
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
int main()
{
int arr[10] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr,sz);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
qsort 函数 - 库函数 - 排序 - quick sort (快速排序)
先来了解一下, qsort 的 结构类型
void qsort(void *base,目标数组(需要排序的数组)
size_t num, 数组元素的个数
size_t width,元素大小(以字节为单位)
int(*compare)(const void *e1, const void *e2)//函数指针,该函数是一个 比较函数
);
void * 介绍
void * : 万能指针,能接受任何 类型的数据,但不能调用,如果要调用,只能强制类型转换,才能使用
#include<stdio.h>
#include<stdlib.h>
int compare(const void* e1, const void* e2)//用来比较两个整形值
// void* 无类型指针,
// void* 类型的指针 = 可以 接收 任意类型 的地址(指针),,但不能进行解引用
// 俗称 万能指针
{
;
}
int main()
{
int a = 10;
void* p = &a;
*p = 0;// void* 无法进行 解引用操作
// 因为 指针的类型,决定它解引用操作时,一次可以访问多少个字节
// 又因为 void* 是一个无类型指针,所以 在它 解引用操作时,无法确定一次访问多少个字节
return 0;
}
进入实战
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int compare(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;// 如果两个宿相等, 返回 0
// 第一个 小于 第二个 返回 复数
} // 第一个 大于 第二个 返回 正数
void test()
{
int arr[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
int sz = sizeof(arr) / sizeof (arr[0]);
qsort(arr, sz, sizeof(arr[0]), compare); // qsort 排序 arr,arr有10个元素,一个元素内存的大小,比较两个元素的大小,返回
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
int compare_f(const void*e1, const void*e2)
{
return (int)(*(float*)e1 - *(float*)e2);
////if (*(float*)e1 == *(float*)e2)
////{
//// return 0;
////}
////else if (*(float*)e1 > *(float*)e2)
////{
//// return 1;
////}
////else
////{
//// return -1;
////}
}
void test2()
{
float f[] = { 9.0, 8.0, 7.0, 6.0, 5.0, 4.0};
int sz = sizeof(f) / sizeof(f[0]);
qsort(f, sz, sizeof(f[0]), compare_f);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%.1f ", f[i]);
}
}
struct stu
{
char name[20];
int age;
};
int compare_by_age(const void*e1, const void*e2)
{
return ((struct stu*) e1)->age - ((struct stu*)e2)->age;
}
void test3()
{
struct stu s[3] = { { "zhangsan", 20 }, { "lisi", 30 }, { "wangwu", 10 } };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), compare_by_age);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", s[i].age);
}
}
int compare_by_name(const void*e1, const void*e2)
{
return strcmp( ((struct stu*) e1)->name,((struct stu*)e2)->name);// 名字比较,就是字符串比较,不能用大于,等于和小于来比较
} // 这里需要 用到 strcmp 函数
// 返回值跟 大小等于的返回值是一样的,0 ,正数,负数
void test4()
{
struct stu s2[3] = { { "zhangsan", 20 },{ "lisi", 30 }, { "wangwu", 10 } };
int sz = sizeof(s2) / sizeof(s2[0]);
qsort(s2, sz, sizeof(s2[0]), compare_by_name);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%s ", s2[i].name);
}
}
int main()
{
test();
printf("\n");
test2();
printf("\n");
test3();
printf("\n");
test4();
return 0;
}
qsort(数组名,该数组元素个数,该数组单个元素内存大小,(函数指针,比较两个元素的 所用函数 函数 的 地址 )函数指针的 两个参数 是:待比较的两个元素的地址
改进冒泡排序(回调函数)
#include<stdio.h>
//实现 bubble_sort函数 的程序员,他 是否知道 未来排序 的 数据类型 - 不知道
//程序员也不知道 待 比较 的 两个元素 的 类型
swap(char* buf1, char*buf2, int width)
{ 我们的 叫换函数,是通过一个字节,一个字节的进行交换
运用了 qsort 参数中的 元素大小,进行实现的
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
int compare_int(const void*e1, const void*e2)
{
return *(int*)e1 - *(int*)e2;
}
void bubble_sort(void*base, int sz, int width, int(*compare)(void*e1, void*e2))// 模拟 qsort 函数
{
int i = 0;
for (i = 0; i < sz; i++)
{
int j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
//两个元素比较,前者比后者大,compare 会返回一个 大于 0 的数字,小于(返回一个差值(负的))相等(返回0)
大于,返回 一个 正数,条件成立,执行 if 语句,进行交换两个元素的位置,其它情况,则不做改动。
if (compare((char*)base + j*width, (char*)base + (j + 1)*width)>0)// 这里实在调用int 函数名(const void*e1, const void*e2)
j 一开始 是 0, base 就是 第一个元素, base+(j+1),就是第二个元素
把他们交给 compare 函数 进行比较,如果 第一个元素 比 第二个元素 大
那就交换,否则,不交换,反上去,下回 j 就是 1,base 就第二个元素,base+(j+1)就是第三个元素
以此类推,
注意!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
((char*)base + j*width,的 char* 和 width(宽度【一个元素的大小】),这里我使用了 qsort 函数参数中 元素个数 和 元素大小,这两点来实现我们的 元素遍历,j*width 等于 跳过几个元素(一个元素的大小 是 width,那 乘上 j,不就是跳过 j 个元素嘛。
{
//交换
由我们自制的交换函数去做
swap((char*)base + j*width, (char*)base + (j + 1)*width, width);
}
}
}
}
void test()
{
int arr[10] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
int sz = sizeof(arr) / sizeof(arr[0]);
// 使用 bubble_sort 的程序员 一定要知道自己排序的是什么数据
//他就应该知道 如何比较待排序数组中的元素
bubble_sort(arr, sz, sizeof(arr[0]), compare_int);
}
struct stu
{
char name[20];
int age;
};
int compare_by_age(const void*e1, const void*e2)
{
return ((struct stu*) e1)->age - ((struct stu*)e2)->age;// 因为 箭头 的 优先级,强制类型转换
}
void test2()
{
struct stu s[3] = { { "zhangsan", 20 }, { "lisi", 30 }, { "wangwu", 10 } };
int sz = sizeof(s) / sizeof(s[0]);
bubble_sort(s, sz, sizeof(s[0]), compare_by_age);
}
int compare_by_name(const void*e1, const void*e2)
{
return strcmp(((struct stu*) e1)->name, ((struct stu*)e2)->name);// 名字比较,就是字符串比较,不能用大于,等于和小于来比较
} // 这里需要 用到 strcmp 函数
//
void test3()
{
struct stu s[3] = { { "zhangsan", 20 }, { "lisi", 30 }, { "wangwu", 10 } };
int sz = sizeof(s) / sizeof(s[0]);
bubble_sort(s, sz, sizeof(s[0]), compare_by_name);
}
int main()
{
test();
printf("\n");
test2();
printf("\n");
test3();
return 0;
}