🌅 一级指针传参
#include <stdio.h> void print(int *p, int sz) //一级指针传参,一级指针接收 { int i = 0; for(i=0; i<sz; i++) { printf("%d\n", *(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]); //一级指针p,传给函数 print(p, sz); return 0; }
一级指针传参,形参可以用一级指针接收,也可以用数组接收(但不推荐)
思考:
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
比如:
void test1(int* p)//test1函数能接收什么参数? { //... } int main() { int a = 10; int* p = &a; int arr[10]; test1(arr); test1(&a); test1(p); return 0; }
分析如下:
形参为一级指针,实参可以是数组,也可以是一级指针(地址)
🌅 二级指针传参
void test(int** ptr) //二级指针接收 { printf("num = %d\n", **ptr); } int main() { int n = 10; int* p = &n; int** pp = &p;//ppa是一个二级指针 test(pp);//二级指针 test(&p);//取地址一级指针,类型为二级指针 return 0; }
二级指针传参时,形参最好用二级指针来接收(指针数组不推荐)
思考:
当函数的参数为二级指针的时候,可以接收什么参数?
比如:
void test(char** p) { } int main() { char c = 'b'; char* pc = &c; char** ppc = &pc; char* arr[10]; test(&pc);//取地址一级指针,类型是二级指针 test(ppc);//传的是二级指针 test(arr);//Ok? ok 因为arr数组名是首元素,char*的地址——类型为char** return 0; }
提问:如果arr2是个二维数组,可以吗?
不可以,要写成char(*p)[5]才可以。
形参为二级指针时,传参可以是二级指针,也可以是数组指针首元素的地址
🐾函数指针
🌅 函数指针的定义
数组指针:是指向数组的指针。
函数指针:类比可知是指向函数的指针,存放函数地址的指针。
int Add(int x, int y) { return x + y; } int main() { int arr[10]; int(*p)[10] = &arr;//p是一个数组指针变量 printf("%p\n", &Add); printf("%p\n", Add); }
我们可以得知:函数名 == &函数名(完全等价)
都可以用来表示函数地址
数组名 != &数组名(完全不同)
函数名 == &函数名(完全等价)
🌅 函数指针的类型
了解了函数指针的定义,那么函数指针类型该怎么样写呢?
int (*pf)(int x, int y) = &Add;//函数指针变量
分析:
接下来,我们试一下这个:int test(char* str)的函数指针咋写?
int test(char* str) {} int main() { int (*pf)(char*) = &test; }
那我们的函数的地址要想保存起来,怎么保存?
下面我们看代码:
void test() { printf("hehe\n"); } //下面pfun1和pfun2哪个有能力存放test函数的地址? void (*pfun1)(); void *pfun2();
首先,能给存储地址,就要求pfun1或者pfun2是指针,那哪个是指针?
答案是:
pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。
阅读两段有趣的代码:
//代码1 (*(void (*)())0)(); //代码2 void (*signal(int , void(*)(int)))(int);
我们慢慢把代码逐层剖析:
代码1: ①:首先是把0强制类型转换成一个函数指针类型,这就意味着0地址处放着一个返回类型是void,无参的一个函数
②:调用0地址处的这个函数
上面的代码是不是太废眼睛了呢?我们接下来用重定义typedef简化一下吧:
typedef void(*pf_t)(int); //给函数指针类型void(*)(int)重新起名叫:pf_t pf_t signal(int, pf_t); //替换后等效于void (*signal(int, void(*)(int)))(int)
注 :推荐《C陷阱和缺陷》,这本书中提及这两个代码。
🐾函数指针数组
🌅 函数指针数组的定义
数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组,
比如:
int *arr[10]; //数组的每个元素是int*
那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?
int (*parr1[10])(); int *parr2[10](); int (*)() parr3[10];
答案是:parr1
parr1 先和 [ ] 结合,说明 parr1是数组,数组的内容是什么呢?
是 int (*)( ) 类型的函数指针。
函数指针数组的用途:转移表
🌅 函数指针数组的实现
例子:(计算器)
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; do{ menu(); int x = 0; int y = 0; int ret = 0; printf("请选择:> "); scanf("%d", &input); printf("请输入2个操作数:> "); scanf("%d %d", &x, &y); switch (input) { case 1: ret = Add(x, y); break; case 2: ret = Div(x, y); break; case 3: ret = Mul(x, y); break; case 4: ret = Div(x, y); break; case 0: printf("退出程序\n"); break; default: printf("重新选择\n"); break; } printf("ret = %d\n", ret); } while (input); return 0; }
我在测试中发现了,有bug!
当input等于0和5的时候,发现程序出错了
接下来我们对此进行修改:
把以下代码加入到switch语句的case中:
printf("请输入2个操作数:> "); scanf("%d %d", &x, &y);
void menu() { printf("**************************\n"); printf("**** 1.add 2.sub ****\n"); printf("**** 3.mul 4.div ****\n"); printf("**** 0.exit ****\n"); printf("**************************\n"); } int main() { int input = 0; int x = 0; int y = 0; int ret = 0; do { menu(); printf("请选择:>"); scanf("%d", &input); switch (input) { case 1: printf("请输入2个操作数:>"); scanf("%d%d", &x, &y); ret = Add(x, y); printf("ret = %d\n", ret); break; case 2: printf("请输入2个操作数:>"); scanf("%d%d", &x, &y); ret = Sub(x, y); printf("ret = %d\n", ret); break; case 3: printf("请输入2个操作数:>"); scanf("%d%d", &x, &y); ret = Mul(x, y); printf("ret = %d\n", ret); break; case 4: printf("请输入2个操作数:>"); 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; }
但是我觉得还不够好:
代码冗余,出现了多次的printf 、scanf(每个case中都有)
可读性低
对此我们利用函数数组指针来优化,通过函数数组的下标来进行跳转到目标函数:
void menu() { printf("**************************\n"); printf("**** 1.add 2.sub ****\n"); printf("**** 3.mul 4.div ****\n"); printf("**** 0.exit ****\n"); printf("**************************\n"); } int main() { int input = 0; int x = 0; int y = 0; int ret = 0; //转移表 int (*pfArr[])(int, int) = {0, Add, Sub, Mul, Div}; //pfArr就是函数数组指针 do { menu(); printf("请选择:>"); scanf("%d", &input); if (input == 0) { printf("退出计算器\n"); } else if(input >= 1 && input<=4) { printf("请输入2个操作数:>"); scanf("%d%d", &x, &y); ret = pfArr[input](x, y); printf("ret = %d\n", ret); } else { printf("选择错误\n"); } } while (input); return 0; }
上面应用了函数指针数组,通过函数数组的下标来进行跳转到对应的目标函数
我们把这种函数指针称为转移表
🐾指向函数指针数组的指针
指向函数指针数组的指针是一个 指针
指针指向一个 数组 ,数组的元素都是 函数指针 ;
如何定义?
void test(const char* str) { printf("%s\n", str); } int main() { //函数指针pfun void (*pfun)(const char*) = test; //函数指针的数组pfunArr void (*pfunArr[5])(const char* str); pfunArr[0] = test; //指向函数指针数组pfunArr的指针ppfunArr void (*(*ppfunArr)[5])(const char*) = &pfunArr; return 0; }
🐾回调函数
🌅回调函数的定义
回调函数就是一个通过函数指针调用的函数
如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
举个简单的例子:
void test() { printf("hehe\n"); } void print_hehe(void (*p)())//函数指针接收,该形参与test函数类型相同 { if (1) p(); } int main() { print_hehe(test); return 0; }
图例分析如下:
把test的地址传给了print_hehe函数,通过print_hehe函数来调用test函数,所以我们称print_hehe函数为回调函数
🌅回调函数的应用
我们拿switch版本的计算机来实践:
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 = 0; int y = 0; printf("请输入2个操作数:>"); 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("退出\n"); break; default: printf("选择错误\n"); break; } } while (input); return 0; }
把冗余的代码封装成一个Calc函数。
把输入的函数地址传给Calc,Calc通过传入的地址,从而找到了目标函数
🐾sqort函数
🌅sqort函数的定义
sqort是一个包含在<stdlib.h> 头文件下的库函数,主要根据一定的比较条件进行快速排序。
也可以对所有类型的数据进行排序,一个函数解决所有类型的排序问题,不需要根据不同的类型些不同的函数,提高效率!🔥
接下来我们来回顾一下冒泡排序:
void bubble_sort(int arr[], int sz) { void bubble_sort(int arr[], int sz) { int i = 0; for (i = 0; i < sz - 1; i++)//排序次数 { int j = 0; for (j = 0; j < sz-1-i; j++)//一次的冒泡对换 { if (arr[j] > arr[j + 1]) { int tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp; } } } } void print_arr(int arr[], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); } int main() { int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };// 排序为升序 int sz = sizeof(arr) / sizeof(arr[0]); print_arr(arr, sz); bubble_sort(arr, sz); print_arr(arr, sz); return 0; }
我们发现:冒泡排序只能对整数进行排序,而不能对其他其他类型的数据进行排序。
对此我们来看看qsort这个函数:
void qsort(void *base, size_t num, size_t width, int (*cmp)(const void *, const void*))
(1):base 👉 待排序数组首地址(可直接输入待排序数组名,或是指向数组的指针)
.
(2):num👉数组中待排序元素数量(可以用sizeof()来求)
.
(3):width👉 各元素的占用空间大小(可以用sizeof(arr[0])来求)
.
(3):cmp 👉 用来比较待排序数据种两个元素的函数。如果,返回的是大于0的数字表示第一个元素大于第二个元素、等于0的话就是表示第一个元素等于第二个元素、小于0的话就是第一个元素小于第二个元素。
ps:程序员们一定要学会看英文资料,对此我们可以下载一个MSDN(可以私聊我拿安装包哦),里面会有各种函数的解析(英文),以后的工作也会接触到英文资料等,加油吧!
那么为什么qsort函数可以排序多种数据类型呢❓
首先:因为void* base指针和num(待排序数组的元素个数)、size(待排序数组的元素大小)可以描述出任意类型。
眼尖的同学会发现👉参数base的类型是viod*
因为void*“海纳百川”,无具体类型,它可以接收任意类型的指针。
接下来我们来看*cmp函数:
这个比较函数指定元素的比较方式,要求使用者自行定义函数。
elem1小于elem2,返回值小于0
elem1大于elem2,返回值大于0
elem1等于elem2,返回值为0
🌅sqort函数的使用
题目:将arr数组当中元素进行排序,用qsort函数实现!
int int_cmp(const void* e1, const void* e2) { return *(int*)e1 - *(int*)e2; } void print_arr(int arr[], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); } int main() { int arr[10] = { 9,8,7,6,5,4,3,2,1,0 }; int sz = sizeof(arr) / sizeof(arr[0]); qsort(arr, sz, sizeof(arr[0]), int_cmp); print_arr(arr, sz); return 0; }
结果:
排序的整型数据:用> <
排序的结构体数据:需要使用者提供一个函数,实现两个数据的比较
那如何用qsort排序结构体呢?
#include<stdlib.h> #include<string.h> #include<stdio.h> struct Stu { char name[20]; int age; }; int sort_by_age(const void* e1, const void* e2)//年龄 { return ((struct Stu*)e1) -> age - ((struct Stu*)e2) -> age;//升序 } int sort_by_name(const void* e1, const void* e2)//名字 { return strcmp(((struct Stu*)e1) -> name, ((struct Stu*)e2) -> name);//升序 } int main() { struct Stu stu[] = { {"zhangsan", 30}, {"lisi", 34}, {"wangwu", 20} }; //按年龄来 qsort(stu, sizeof(s) / sizeof(s[0]), sizeof(s[0]), sort_by_age); //按名字来 qsort(stu, sizeof(s) / sizeof(s[0]), sizeof(s[0]), sort_by_name); return 0; }
e1 - e2为升序;e2 - e1为降序
我们摸透了sqort接下来模拟实现一下吧
🌅sqort函数的模拟实现
(采用冒泡的方式)
void swap(char* buf1, char* buf2, int width) { for (int i = 0; i < width; i++) { char t = *buf1; *buf1 = *buf2; *buf2 = t; buf1++; buf2++; } } int cmp_int(const void* e1, const void* e2) { return (*(int*)e1 - *(int*)e2);//void强制类型转换为元素的地址的类型 } void bubble_sort(void* base, int num, int width,int(*cmp)(const void* e1, const void* e2)) { for (int i = 0; i < num - 1; i++) { for (int j = 0; j < num - 1 - i; j++) { //if(arr[j]>arr[j+1])比较 if (cmp((char*)base + (j)*width, (char*)base + (j + 1) * width) > 0) { //交换 // int t=arr[j]; // arr[j]=arr[j+1]; // arr[j+1]=t; swap((char*)base + (j)*width, (char*)base + (j + 1) * width,width); } } } } void print_arr(int arr[], int sz) { for (int i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); } int main() { int arr[] = { 9,8,7,6,5,4,3,2,1,0 }; int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);//0 1 2 3 4 5 6 7 8 9 print_arr(arr, sz); }
因为base是void*类型 ,并且语法不支持(指针±整数),而char类型是所以类型中的最小的。所以(char)base + j * size可以确定元素地址。
📢写在最后
能看到这里的都是棒棒哒🙌!
想必指针也算是C语言中最难🔥的部分了,如果认真看完以上部分,肯定有所收获。
指针内容比较难懂,我们一定多敲代码,都说了2w行代码才算入门了C语言!你写了多少行呢?
接下来我还会继续写关于📚《指针练习题目》等…
💯如有错误可以尽管指出💯
🥇想学吗?我教你啊🥇
🎉🎉觉得博主写的还不错的可以一键三连撒🎉🎉