这个章节,我们继续探讨指针的高级主题
1. 字符指针
1.1 使用
** 1.1.1 对字符使用 一般我们对字符指针的使用如下:**
#include <stdio.h> int mian() { char ch = 'w'; char* pc = &ch; return 0; }
** 1.1.2 对字符串使用**
** 而字符指针可以对字符串进行定义,但是只指向字符串的首元素地址,解引用访问时,只访问首地址的内容,即第一个元素,代码如下:**
#include <stdio.h> int mian() { char* ps = "abcdef"; printf("%c\n", *ps); return 0; }
** 此处存在问题,如果用的是2022版本的vs,此时会警告,当我们使用这种方式去初始化指针,我们对ps内存放的字符进行修改,我们发现程序会崩溃,常量字符串不可以修改。**
int main() { char ch = 'w'; char* ps = "abcdef"; *ps = 'w'; printf("%c\n", *ps); return 0; }
** 我们可以在前面用const修饰一下,这样就可以啦,如下:**
int main() { char ch = 'w'; const char* ps = "abcdef"; // *ps = 'w'; printf("%s\n", ps); return 0; }
** 打印的时候,打印字符串%s,给出首地址即可 printf("%s\n", ps);**
1.1.3 对字符数组使用
** 如果想修改,我们可以定义字符数组:**
#include <stdio.h> int main() { char arr[] = "abcdef"; char* p = arr; return 0; }
1.2 面试题
#include <stdio.h> int main() { char str1[] = "hello you."; char str2[] = "hello you."; const char* str3 = "hello you."; const char* str4 = "hello you."; if (str1 == str2) printf("str1 and str2 are same\n"); else printf("str1 and str2 are not same\n"); if (str3 == str4) printf("str3 and str4 are same\n"); else printf("str3 and str4 are not same\n"); return 0; }
** 我们来详细解释一下这个题目,const修饰的指针str3\str4是无法改变的,既然存的是一样的并且无法改变的内容,为了优化内存,只开辟同一个空间来存储内容,所指向的是同一个地址;但是str1\str2是两个独立的空间,所指向的地址也是两个独立空间的不同的地址,此题比较的不是内容,比较的是地址,图解如下:**
** 如果要比较字符串的内容,要用strcmp,此处不过多介绍。**
2. 指针数组
2.1 基本使用
** 我们初始化一个指针数组:**
int main() { char* arr[5] = { "zbcdef","zhangsan","hehe","wangcai","ruhua" }; return 0; }
** 我们将其打印:**
int main() { char* arr[5] = { "zbcdef","zhangsan","hehe","wangcai","ruhua" }; int i = 0; for (i = 0; i < 5; i++) { printf("%s\n", arr[i]); } return 0; }
2.2 一维数组模拟二维数组
int main() { int arr1[5] = { 1,2,3,4,5 }; int arr2[5] = { 2,3,4,5,6 }; int arr3[5] = { 3,4,5,6,7 }; int arr4[5] = { 5,6,7,8,9 }; int* arr[4] = { arr1,arr2,arr3,arr4 }; return 0; }
**将其打印:**
//用一维数组模拟二维数组 int main() { int arr1[] = { 1,2,3,4,5}; int arr2[] = { 2,3,4,5,6}; int arr3[] = { 3,4,5,6,7}; int arr4[] = { 4,5,6,7,8}; int* arr[4] = { arr1,arr2,arr3,arr4 }; int i = 0; for (i = 0; i < 4; i++) { int j = 0; for (j = 0; j < 5; j++) { printf("%d ", arr[i][j]); //同数组的引用,不需要解引用,我们拿到的也是数组首地址,再对其操作 //arr[j]=*(arr+j),可以看成已经解引用过 } printf("\n"); } return 0; }
2.3 总结
int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组
3. 数组指针
3.1 定义
** 指向数组地址的指针**
//整型指针——指向整型的指针——存放整型变量的地址 //int* p1; //字符指针——指向字符的指针——存放字符变量的地址 //char* p2; //数组指针——指向数组的指针——存放数组的地址 int main() { int a = 10; int* p1 = &a; char ch = 'w'; char* p2 = &ch; int arr[10] = { 1,2,3,4,5 }; int (* pa)[10] = &arr;//取出的是数组的地址,存放到pa中,pa是数组指针变量 //int(*)[10]——数组指针类型 return 0; }
** 数组指针定义时,(pa)说明这是指针,然后结合【10】说明是数组,int说明其指向的是整型数组,说明这是指向整型数组的指针。int()[10]——数组指针类型**
int (*p)[10];
//解释:pa先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以pa是一个 指针,指向一个数组,叫数组指针。
//这里要注意:[]的优先级要高于号的,所以必须加上()来保证pa先和结合。
3.2 数组名&数组名
int main() { int arr[10]; printf("%p\n", arr); printf("%p\n", &arr[0]); printf("%p\n", &arr); return 0; }
** **
** 数组的首地址,数组首元素的地址,数组的地址均指向同一个地址,从数值上看是一样的。我们再来看看区别:**
int main() { int arr[10]; printf("%p\n", arr); printf("%p\n", arr+1); printf("%p\n", &arr[0]); printf("%p\n", &arr[0]+1); printf("%p\n", &arr); printf("%p\n", &arr+1); return 0; }
** 指针的加减跳过的字节取决于指针的类型**
3.3特殊
//数组名是数组首元素的地址
两个例外
1.sizeof(数组名)(此时表示整个数组)
2.&数组名
3.4 如何使用
3.4.1 举个例子(一维数组)
void printf1(int arr[10], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); } int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int sz = sizeof(arr) / sizeof(arr[0]); printf1(arr, sz); return 0; }
** 我们在传首地址时,相当于传arr,然后我们可以修改为:*
void printf1(int *arr, int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", *(arr+i)); } printf("\n"); } int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int sz = sizeof(arr) / sizeof(arr[0]); printf1(arr, sz); return 0; }
** 我们进而修改为数组指针来使用,如下:**
void printf1(int(*p)[10], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", (*p)[i]);//p内存放arr首地址,*p==*&arr==arr //为了取出数组元素,(*p)[i]==arr[i]; } printf("\n"); } int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int sz = sizeof(arr) / sizeof(arr[0]); printf1(&arr, sz); return 0; }
** p内存放arr首地址,p==&arr==arr;为了取出数组元素,(p)[i]==arr[i];*
但是一维数组里,我们使用数组指针终究是麻烦的,一般不适合用于一维数组情况。
3.4.2 举个栗子(二维数组)
** 我们打印一个二维数组:**
数组传参,数组接收:
printf3(int arr[3][5], int r, int c)
{
int i = 0, j = 0;
for (i = 0; i < r; i++)
{
for (j = 0; j < c; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
void test2()
{
int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6,},{3,4,5,6,7} };
printf3(arr,3,5);
}
int main()
{
test2();
return 0;
}
数组传参,指针接收(数组指针常用形式)
二维数组的首元素,就是二维数组的第一行;
arr是二维数组的数组名,数组名是数组首元素的地址,arr就是第一行的地址
#include<stdio.h> void printf4(int(*p)[5],int r,int c ) { int i = 0; for (i = 0; i < r; i++) { int j = 0; for (j = 0; j < c; j++) { printf("%d ", * (*(p + i) + j));//p+i指向某一行,解引用找出某一行 //对某一行的首地址加j,再解引用找出元素 } printf("\n"); } } void test2() { int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6,},{3,4,5,6,7} }; printf4(arr,3,5); } int main() { test2(); return 0; }
3.5 小结
**int arr[5]; 整形数组,五个整型元素的数组
int parr1[10]; 指针数组,十个指针元素的数组
int (parr2)[10]; 数组指针,指向数组的指针*int (parr3[10])[5]; 数组
** parr3有十个元素,是存放数组指针的数组**
4. 数组传参和指针传参
4.1 一维数组传参
#include <stdio.h> void test(int arr[])//ok? 正确,数组传参,形参可以是数组,也可以是指针 // 数组传参,传的不是整个数组,不需要创建一个数组,可以不写大小 {} void test(int arr[10])//ok? 正确,多写了大小,虽然大小没有实际作用 {} void test(int* arr)//ok? 正确,形参是指针 {} void test2(int* arr[20])//ok? 正确,形参是数组,同实参,也可以省略数组大小 {} void test2(int** arr)//ok? 正确,详细见图 {} int main() { int arr[10] = { 0 }; //整型数组 int* arr2[20] = { 0 }; //整形指针数组 test(arr); test2(arr2); }
4.2 二维数组传参
void test(int arr[3][5])//ok? 形参是数组,正确 {} void test(int arr[][])//ok? 形参是数组,错误,不能均省略 {} void test(int arr[][5])//ok? 形参是数组,正确,可以只有列数 {} //总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。 //因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。 //这样才方便运算。 void test(int* arr)//ok? 形参是指针,一维数组 {} //指向arr第一行的首地址,操作会产生问题 void test(int* arr[5])//ok? 此处arr与【5】结合,是数组 {} //我们想要形参是指针 void test(int(*arr)[5])//ok? 形参指针,每个指针指向一个含五个元素的整型数组 {} void test(int** arr)//ok? 错误,传出去arr是行地址,一维数组的地址 {} //不能用二级指针,二级指针接收一级指针的地址 int main() { int arr[3][5] = { 0 }; test(arr); }
4.3 一级指针传参
#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 test(int* p)//参数是一级指针时 { } int main() { int a = 0; test(&a);//ok 传变量地址 int* ptr = &a; test(ptr);// 传一级指针本身 int arr[10]; test(arr);// 传一维数组数组名 }
4.4 二级指针传参
#include <stdio.h> void test(int** ptr) { printf("num = %d\n", **ptr); } int main() { int n = 10; int*p = &n; int **pp = &p; test(pp); test(&p); return 0; }
当函数的参数为二级指针的时候,可以接收什么参数?
void test(int** p) { } int main() { int** ptr = ; test(ptr);//ok 传二级指针 int* p2 = ; test(&p2);// 传一级指针地址 int* arr[10]; test(arr);// 传指针数组的数组名 }
5. 函数指针
5.1 引入介绍
int Add(int x, int y) { return x + y; } int main() { int arr[10] = { 0 }; printf("%p\n", &arr); printf("%p\n", arr); printf("%p\n", Add); printf("%p\n", &Add); return 0; }
**数组名与取地址数组名的值相同,但是意义不一样,一个是数组首元素的地址,一个是数组的地址**
** **** 对于函数名,两个没有区别,函数名与取地址函数名拿到的都是函数地址。**
对于函数指针,假定指针pf指向函数Add,声明这是一个指针变量,(pf),其类型是int函数,且函数的参数是int,我们可以写出 int(pf)(int x,int y)=&Add;
//pf就是函数指针变量 int (*pf)(int x, int y) = &Add; //int (*pf)(int x, int y) = Add;或者这样写
** 我们定义了一个函数指针,那么我们如何调用它呢?**
int Add(int x, int y) { return x + y; } int main() { //pf就是函数指针变量 int (*pf)(int x, int y) = &Add; int sum=(*pf)(3, 5);//对pf解引用找到Add函数,使用Add函数 printf("%d\n", sum); return 0; }
** 我们考虑到Add是存放于pf中的,类比int sum=Add(3,5);int sum=(pf)(3,5);*
我们能否去掉前面的解引用符号,直接使用pf代替Add呢?
int Add(int x, int y) { return x + y; } int main() { //pf就是函数指针变量 int (*pf)(int x, int y) = &Add; int sum=pf(3, 5); printf("%d\n", sum); return 0; }
** 所以也是可以的,由此可知此处没有实际作用,也可以多写几个,加帮助理解,但是语法上没有用。**
int Add(int x, int y) { return x + y; } int main() { //pf就是函数指针变量 int (*pf)(int x, int y) = &Add; int sum=(*******pf)(3, 5); printf("%d\n", sum); return 0; }
5.2 ((void ()() )0 )();
** 我们来解读一下这个有意思的代码**
int main() { (*(void (*)() )0 )(); return 0; }
** 0前面的()内放置void ()();我们对其补充void (p)(),这是一个函数指针,且函数的返回类型为void。
所以0前面的括号内放置了一个类型,而类型放在()里,强制类型转换。
(void ()() )0——把0当做一个函数的地址,0强制类型转换成像void ()()的函数类型的地址,0地址内存放这样的函数。
前面加去找到这个地址内存放的这样类型的函数,并调用函数,无参加上()。*** 把0直接转换成一个void()()的函数指针,然后去调用0的地址处的函数,前面解引用的同5.1的内容,是可以省略的**
*5.3 void (signal(int , void()(int)))(int);*
int main() { void ( *signal(int, void(*)(int) ) )(int); return 0; }
** 上述代码是一个函数声明,声明的函数叫signal,signal函数的第一个参数是int类型,第二个参数是一个函数指针类型,该函数指针指向的函数参数是int,返回类型是void,signal函数的返回类型也是函数指针类型,该函数指针指向的函数参数是int,返回类型是void。**
** 如何简化呢?**
//void ( *signal(int, void(*)(int) ) )(int); //函数类型及返回类型都是void(*)(int),我们typedef 一下 //typedef void(*)(int) pf_t;但是语法要求pf_t应该放置于*旁边 typedef void(* pf_t)(int); pf_t signal(int, pf_t);
6. 函数指针数组——转移表
6.1 介绍
int p;//整型指针*
int arr【5】;//整型指针数组*
函数指针
int(*pf)(int,int);
函数指针数组
*int (pfArr[4])(int,int);
其可以存放多个参数相同,及返回类型相同的函数地址
int Add(int x, int y) { return x + y; } int Sub(int x, int y) { return x - y; } int main() { int(*pfArr[2])(int, int) = { Add,Sub }; return 0; }
我们尝试调用这个函数指针数组
int Add(int x, int y) { return x + y; } int Sub(int x, int y) { return x - y; } int main() { int(*pfArr[2])(int, int) = { Add,Sub }; int ret = pfArr[0](2, 3); printf("%d\n", ret); ret = pfArr[1](2, 3); printf("%d\n", ret); return 0; }
6.2 应用——写一个整数计算器
6.2.1 函数主体
为了能够不断地计算,我们可以利用do while循环,为了使计算器的打印更明确,我们写一个菜单函数,如下:
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; do { menu(); printf("请选择:>"); scanf("%d", &input); switch (input) { case 1: break; case 2: break; case 3: break; case 4: break; case 0: break; default: break; } }while(input) return 0; }
6.2.2 各函数块的编写
一、Add函数的初步编写
** 我们如果需要键入操作1,那么我们调用Add函数,此时我们还需键入两个操作数,x,y,操作后的结果,我们用ret来接收,由此可写出如下的代码**
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); printf("请输入两个操作数:>"); scanf("%d %d", &x, &y); switch (input) { case 1: ret=Add(x, y); printf("%d\n", ret); break; case 2: break; case 3: break; case 4: break; case 0: break; default: break; } }while(input) return 0; }
二、 其他函数块的初步编写
** 同理,我们可以编写如下的部分**
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); printf("请输入两个操作数:>"); scanf("%d %d", &x, &y); switch (input) { case 1: ret=Add(x, y); printf("%d\n", ret); break; case 2: ret = Sub(x, y); printf("%d\n", ret); break; case 3: ret = Mul(x, y); printf("%d\n", ret); break; case 4: ret = Div(x, y); printf("%d\n", ret); break; case 0: printf("退出计算器\n"); break; default: printf("选择错误\n"); break; } }while(input) return 0; }
6.2.3 各函数详细编写
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 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); printf("请输入两个操作数:>"); scanf("%d %d", &x, &y); switch (input) { case 1: ret = Add(x, y); printf("%d\n", ret); break; case 2: ret = Sub(x, y); printf("%d\n", ret); break; case 3: ret = Mul(x, y); printf("%d\n", ret); break; case 4: ret = Div(x, y); printf("%d\n", ret); break; case 0: printf("退出计算器\n"); break; default: printf("选择错误\n"); break; } } while (input); return 0; }
** 6.2.4 完善改进**
** 我们发现其中一个问题,当我们只输入一个操作数的时候,其不报错,我们可以对其进行修改,修改后代码如下:**
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 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("请输入两个操作数:>"); scanf("%d %d", &x, &y); ret = Add(x, y); printf("%d\n", ret); break; case 2: printf("请输入两个操作数:>"); scanf("%d %d", &x, &y); ret = Sub(x, y); printf("%d\n", ret); break; case 3: printf("请输入两个操作数:>"); scanf("%d %d", &x, &y); ret = Mul(x, y); printf("%d\n", ret); break; case 4: printf("请输入两个操作数:>"); scanf("%d %d", &x, &y); ret = Div(x, y); printf("%d\n", ret); break; case 0: printf("退出计算器\n"); break; default: printf("选择错误\n"); break; } } while (input); return 0; }
** 而且我们的代码日后若想增加更多的功能,其会越写越长。我们需要简化代码。我们可以定义一个函数指针数组—— int (pfArr[5])(int ,int) = { 0,Add,Sub,Mul,Div };*
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 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[5])(int ,int) = { 0,Add,Sub,Mul,Div }; do { menu(); printf("请选择:>"); scanf("%d", &input); printf("请输入两个操作数:>"); scanf("%d %d", &x, &y); ret=pfArr[input](x, y); printf("%d\n", ret); } while (input); return 0; }
** 此时代码能够正常运行,但是会有一些问题:**
** 当我们输入0时,其也让我们输入两个操作数,我们对input的内容进行条件判断**
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 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[5])(int ,int) = { 0,Add,Sub,Mul,Div }; do { menu(); printf("请选择:>"); scanf("%d", &input); if (input == 0) { printf("退出计算器\n"); break; } if (input >= 1 && input <= 4) { printf("请输入两个操作数:>"); scanf("%d %d", &x, &y); ret = pfArr[input](x, y); printf("%d\n", ret); } else { printf("选择错误\n"); } } while (input); return 0; }
** 此时计算器的编写已经比较完善了,也方便后续对计算器功能的增加**
7. 指向函数指针数组的指针
函数指针
int (*pf)(int ,int);
函数指针数组
int (*pfArr[4])(int ,int);
指向函数指针数组的指针
应该是什么什么=&pfArr;
函数指针数组的指针类型,我们在函数指针数组的类型上进行修改int (*ptr[4])(int ,int);
改名称int (*(*ptr)[4])(int ,int);
其是指针,不是数组,所以可得int (*(*ptr)[4])(int ,int) =&pfArr;
8. 回调函数
8.1 定义及介绍
** 回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。**
8.2 举个例子
** 我们还是用计算器作为我们的例子,我们回到第一次修改之后的计算器代码,如下:**
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 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("请输入两个操作数:>"); scanf("%d %d", &x, &y); ret = Add(x, y); printf("%d\n", ret); break; case 2: printf("请输入两个操作数:>"); scanf("%d %d", &x, &y); ret = Sub(x, y); printf("%d\n", ret); break; case 3: printf("请输入两个操作数:>"); scanf("%d %d", &x, &y); ret = Mul(x, y); printf("%d\n", ret); break; case 4: printf("请输入两个操作数:>"); scanf("%d %d", &x, &y); ret = Div(x, y); printf("%d\n", ret); break; case 0: printf("退出计算器\n"); break; default: printf("选择错误\n"); break; } } while (input); return 0; }
** **
** 我们发现红色框内的代码十分相似,代码显得重复繁冗,我们尝试用calc函数来代替红色框内的代码,但是红色框内的计算方式不同,我们同样的需要去调用计算的函数,我们去调用函数的地址,而函数的地址,应该放入函数的指针,假定为p,参数是int ,int ,返回类型是int ,void calc(int(p)(int,int)),例如我们取Add地址,即void calc(int(Add)(int,int)),
我们调用p即调用相对应的传地址的计算函数,ret = p(x, y);,例如Add,即ret=Add(x.y)代码修改如下:#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 menu() { printf("************************\n"); printf("**** 1.add 2.sub ***\n"); printf("*****3.mul 4.div ***\n"); printf("*****0.exit ***\n"); printf("************************\n"); } void calc(int(*p)(int,int)) { int x = 0; int y = 0; int ret = 0; printf("请输入两个操作数:>"); scanf("%d %d", &x, &y); ret = p(x, y); printf("%d\n", ret); } int main() { int input = 0; do { menu(); printf("请选择:>"); scanf("%d", &input); printf("请输入两个操作数:>"); scanf("%d %d", &x, &y); 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; }
** 当p去调用Add时,Add就是回调函数;当p去调用Sub时,Sub就是回调函数。**
9 qsort函数
qsort函数——c语言标准库提供的排序函数,该函数的排序是快速排序
9.1 对冒泡排序的回顾
** 图解冒泡排序**
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(int arr[], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); } int main() { //冒泡排序 //对整型数据进行排序 int arr[] = { 2,1,3,7,5,9,6,8,0,4 }; int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr, sz); print(arr, sz); return 0; }
** 而bubble_sort冒泡排序的缺陷也非常明显,其只能对整数进行冒泡排序,所以我们来看看qsort函数。**
9.2 qsort 详解(可排序任意类型的数据)
** 我们检索一下它的用法:**
void qsort (void* base, size_t num, size_t size, int (*compar)(const void*,const void*)); void qsort(void* base, size_t num, size_t size, int (*compar)(const void*, const void*)//函数指针//compar比较,*,其是个指针, //指向函数,函数参数是const void* const void* //函数返回类型是int
使用时要引入头文件#include<stdlib.h>
** base 是指向待排序的数组的第一个对象的指针,该对象将转换为 .
void*
**
num 数组中由指向的元素数。即上文的sz
size 数组中每个元素的大小(以字节为单位)。指向比较两个元素的函数的指针。
compar 此函数被重复调用 by 以比较两个元素。
** 对于void* base,由于qsort函数可以排序任何类型,而base是指向待排序数组的第一个对象的指针,由于待排序的数组可以是任意类型,所以我们不能直接对其定义指针类型,例如int*,char*等,这样我们一旦去处理其他类型的时候都会出现问题,由于传递的指针可能是int,char等类型,我们可以用void*来处理,void*可以接收任意指针类型的地址。**
** 但是其解引用的时候,需要先强制类型转换,再解引用,例如 voidp=&i;*
(int )p=200;也不能直接p++的操作。
** num,我们排序的时候需要知道待排序元素的个数,得清楚自己需要排多少。**
** size,我们需要知道待排序元素的大小,因为我们不知道元素类型,所以当我们知道元素的大小,就知道其偏移量,就能够找到下一个元素的地址,找到下一个元素。**
** compar比较两个元素的大小的函数指针,会比较才能排序。**
比较:
两个整型使用关系运算符比较大小;
两个字符串,使用strcmp比较大小;
两个结构体,也得制定比较方式;
由于比较的方式不同,所以比较的时候需要用不同的方法,用哪个传哪个,所以增加一个compar函数,其需要待比较的两个元素的指针,返回类型是int
9.3 整型排序
9.3.1 整型升序排序
** 我们来尝试写一下整型升序排序代码,如下:**
#include<stdio.h> #include<stdlib.h> void print(int arr[], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); } int cmp_int(const void* e1, const void* e2) { //此处明确e1,e2类型,可强制转换类型 if (*(int*)e1 > *(int*)e2) return 1; else if (*(int*)e1 < *(int*)e2) return -1; else return 0; } //测试qsort函数功能 void test2() { int arr[] = { 2,1,3,7,5,9,6,8,0,4 }; int sz = sizeof(arr) / sizeof(arr[0]); qsort(arr, sz, sizeof(arr[0]), cmp_int); print(arr, sz); } int main() { test2(); return 0; }
** 此处比较写得比较繁冗,我们对其cmp_int 部分修改,如下:**
#include<stdio.h> #include<stdlib.h> void print(int arr[], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); } int cmp_int(const void* e1, const void* e2) { //此处明确e1,e2类型,可强制转换类型 return (*(int*)e1 - *(int*)e2); } //测试qsort函数功能 void test2() { int arr[] = { 2,1,3,7,5,9,6,8,0,4 }; int sz = sizeof(arr) / sizeof(arr[0]); qsort(arr, sz, sizeof(arr[0]), cmp_int); print(arr, sz); } int main() { test2(); return 0; }
9.3.2 整型降序排序
** 我们再进一步修改成整型降序排列的代码,如下:**
#include<stdio.h> #include<stdlib.h> void print(int arr[], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); } int cmp_int(const void* e1, const void* e2) { //此处明确e1,e2类型,可强制转换类型 return (*(int*)e2 - *(int*)e1); } //测试qsort函数功能 void test2() { int arr[] = { 2,1,3,7,5,9,6,8,0,4 }; int sz = sizeof(arr) / sizeof(arr[0]); qsort(arr, sz, sizeof(arr[0]), cmp_int); print(arr, sz); } int main() { test2(); return 0; }
9.4 结构体排序
9.4.1 按name排序
** 我们再来尝试写一下结构体数据的排序,我们假设一个含有name与年龄的结构体,我们按其名字进行排序:**
** 我们需要用到strcmp函数,其返回值同qsort,可以直接return,不需要if else判断,其使用需要引入#include<string.h>头文件**
#include<stdio.h> #include<string.h> #include<stdlib.h> struct Stu { char name[20]; int age; }; int cmp_by_name(const void* e1, const void* e2) { return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name); } //测试qsort排序结构体数据 void test3() { struct Stu s[] = { {"zhangsan",20},{"lisi",55},{"wangwu",40} }; //按照名字比较 int sz = sizeof(s) / sizeof(s[0]); qsort(s, sz, sizeof(s[0]), cmp_by_name); } int main() { //test2(); test3(); return 0; }
9.4.2 按年龄排序
一、升序
#include<stdio.h> #include<string.h> #include<stdlib.h> struct Stu { char name[20]; int age; }; int cmp_by_age(const void* e1, const void* e2) { return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age; } //测试qsort排序结构体数据 void test3() { struct Stu s[] = { {"zhangsan",20},{"lisi",55},{"wangwu",40} }; int sz = sizeof(s) / sizeof(s[0]); //按年龄 qsort(s, sz, sizeof(s[0]), cmp_by_age); } int main() { //test2(); test3(); return 0; }
二、降序
#include<stdio.h> #include<string.h> #include<stdlib.h> struct Stu { char name[20]; int age; }; int cmp_by_age(const void* e1, const void* e2) { return ((struct Stu*)e2)->age - ((struct Stu*)e1)->age; } //测试qsort排序结构体数据 void test3() { struct Stu s[] = { {"zhangsan",20},{"lisi",55},{"wangwu",40} }; int sz = sizeof(s) / sizeof(s[0]); //按年龄 qsort(s, sz, sizeof(s[0]), cmp_by_age); } int main() { //test2(); test3(); return 0; }
9.5 用冒泡排序模拟qsort
9.5.1 模拟整数排序
** 我们学习了qsort的逻辑结构,我们能否用此逻辑结构来完善改进我们的冒泡排序呢?** **我们接收的可能是任意类型的数据,所以红框部分的代码需要修改,我们考虑到比较的地址指向的类型不确定,所以base参数的类型我们写作void*,其指向待比较数据的地址;**
我们还需知道比较的个数sz;
比较的数据的大小width,单位是字节;
不同类型数据的比较方法不同,我们还需一个比较函数,来实现不同类型数据的比较,该cmp函数的参数是const void e1,const void e2,其返回类型是int.**
冒泡排序的内部只需修改比较部分,我们比较的两个参数该如何找到,首先我们对base强制类型转换成char类型,然后该指针类型已经转换完成后,我们再对其+-,比如(char*)base+j*width与(char*)base+(j+1)*width进行比较。找到 j 下标的元素,与 j+1 下标的元素进行比较。 交换部分,如果是再创建一个变量,由于我们不知道内部存储什么类型的变量,我们无法明确创建什么类型的变量,我们考虑数据存储时,小端存储内四个字节,我们将对应字节内部的数据进行交换即可,即两个整型的相互交换,所以我们写一个Swap函数,来交换我们需要交换的数据,同时我们也需要知道数据所占的字节大小,即width。所以我们可以写出
void Swap(char* buf1,char* buf2,int width) { }
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
** 图解:**
** 具体代码如下:**
#include<stdio.h> void Swap(char* buf1,char* buf2,int width) { int i = 0; for (i = 0; i < width; i++) { char tmp = *buf1; *buf1 = *buf2; *buf2 = tmp; buf1++; buf2++; } } void bubble_sort(void* base,int sz,int width,int (*cmp)(const void* e1,const void* e2)) { int i = 0; //趟数 for (i = 0; i < sz - 1; i++) { //一趟冒泡排序的过程 int j = 0; for (j = 0; j < sz - 1 - i; j++) { if (cmp((char*)base + j * width, (char*)base + (j + 1) * width)>0) { //交换 Swap((char*)base + j * width, (char*)base + (j + 1) * width, width); } } } } int cmp_int(const void* e1, const void* e2) { //此处明确e1,e2类型,可强制转换类型 return (*(int*)e1 - *(int*)e2); } void print(int arr[], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); } //冒泡排序 //对整型数据进行排序 void test4() { int arr[] = { 2,1,3,7,5,9,6,8,0,4 }; int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr, sz, sizeof(arr[0]), cmp_int); print(arr, sz); } int main() { test4(); return 0; }
9.5.2 模拟结构体按name排序
** 图解:**
#include<stdio.h> struct Stu { char name[20]; int age; }; void Swap(char* buf1,char* buf2,int width) { int i = 0; for (i = 0; i < width; i++) { char tmp = *buf1; *buf1 = *buf2; *buf2 = tmp; buf1++; buf2++; } } void bubble_sort(void* base,int sz,int width,int (*cmp)(const void* e1,const void* e2)) { int i = 0; //趟数 for (i = 0; i < sz - 1; i++) { //一趟冒泡排序的过程 int j = 0; for (j = 0; j < sz - 1 - i; j++) { if (cmp((char*)base + j * width, (char*)base + (j + 1) * width)>0) { //交换 Swap((char*)base + j * width, (char*)base + (j + 1) * width, width); } } } } int cmp_int(const void* e1, const void* e2) { //此处明确e1,e2类型,可强制转换类型 return (*(int*)e1 - *(int*)e2); } int cmp_by_name(const void* e1, const void* e2) { return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name); } void print(int arr[], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); } void test5() { struct Stu s[] = { {"zhangsan",20},{"lisi",55},{"wangwu",40} }; //按照名字比较 int sz = sizeof(s) / sizeof(s[0]); bubble_sort(s, sz, sizeof(s[0]), cmp_by_name); } int main() { test5(); return 0; }
9.5.3 模拟结构体按age排序
#include<stdio.h> struct Stu { char name[20]; int age; }; void Swap(char* buf1,char* buf2,int width) { int i = 0; for (i = 0; i < width; i++) { char tmp = *buf1; *buf1 = *buf2; *buf2 = tmp; buf1++; buf2++; } } void bubble_sort(void* base,int sz,int width,int (*cmp)(const void* e1,const void* e2)) { int i = 0; //趟数 for (i = 0; i < sz - 1; i++) { //一趟冒泡排序的过程 int j = 0; for (j = 0; j < sz - 1 - i; j++) { if (cmp((char*)base + j * width, (char*)base + (j + 1) * width)>0) { //交换 Swap((char*)base + j * width, (char*)base + (j + 1) * width, width); } } } } int cmp_int(const void* e1, const void* e2) { //此处明确e1,e2类型,可强制转换类型 return (*(int*)e1 - *(int*)e2); } int cmp_by_age(const void* e1, const void* e2) { return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age; } void print(int arr[], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); } void test5() { struct Stu s[] = { {"zhangsan",20},{"lisi",55},{"wangwu",40} }; int sz = sizeof(s) / sizeof(s[0]); bubble_sort(s, sz, sizeof(s[0]), cmp_by_age); } int main() { test5(); return 0; }
版权归原作者 komorebi-filpped 所有, 如有侵权,请联系我们删除。