在指针篇1我们已经了解了整型指针,当然还有很多其他类型的指针,像字符指针、数组指针、函数指针等,他们都有他们的特别之处,让我们接着学习。
1. 指针类型介绍和应用
1.1 字符指针变量
字符指针变量类型为char*,一般这样使用:
int main()
{char ch = 'w';char *pc = &ch;*pc = 'w';return 0;
}
还有一种使用方式:
int main()
{const char* pstr = "hello bit.";//这⾥是把⼀个字符的地址传给pstr变量printf("%s\n", pstr);return 0;
}
1.2 数组指针变量(与指针数组区分)
提到数组指针,我们会想数组指针到底是数组还是指针,指针数组又是什么,他们怎么区分?
我们可以这样理解:
数组指针,就是一个存放内容是数组的指针,那自然是指针了
指针数组,就是一个数组元素是指针的数组,那自然是数组了
我们也可以通过后缀来理解,后缀是本质,前者都是形容词。
知道了两者的区别后我们看一段代码:
int *p1[10]; //指针数组
int (*p2)[10];//数组指针
int *p1[10] 解释:
[ ]的优先级高于*,p1先与[10]结合,表明这是一个数组,前面的int*表明数组中的元素是int*类型的,所以这是一个指针数组。
int (*p2)[10]解释:因为有()的存在,p2先与*结合,说明p2是一个指针变量,然后指针指向的是一个大小为10个整型的数组,所以这是一个数组指针。
1.3 函数指针变量
通过前面的学习我们不难猜到函数指针是用来存放函数地址的,未来可以通过函数地址调用函数。
函数的地址与数组有相似之处,函数名就是函数的地址,也可以通过&函数名的方式获得函数的地址。我们要将函数的地址存放起来,就需要创建函数指针变量了,函数指针变量的写法也和数组指针非常相似,如下代码:
void test()
{printf("hehe\n");
}void (*pf1)() = &test;
void (*pf2)()= test;int Add(int x, int y)
{return x+y;
}int(*pf3)(int, int) = Add;
int(*pf4)(int x, int y) = &Add;//x和y写上或者省略都是可以的
上面说到可以通过函数地址调用函数,函数地址存放在函数指针变量中,我们是不是可以通过函数指针调用指针指向的函数呢?我们通过代码实现一下。
#include <stdio.h>int Add(int x, int y){return x+y;}int main(){int(*pf3)(int, int) = Add;printf("%d\n", (*pf3)(2, 3));//输出5printf("%d\n", pf3(3, 5)); //输出8return 0;}
2. 二维数组的本质
有了数组指针的理解,我们就可以讲一讲二维数组传参的本质了
过去二维数组要传参给一个函数时,代码如下:
#include <stdio.h>void test(int a[3][5], int r, int c)
{int i = 0;int j = 0;for(i=0; i<r; i++){for(j=0; j<c; j++){printf("%d ", a[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}};test(arr, 3, 5);return 0;
}
这里,形参除了写成二维数组的形式,还可以怎么写?
我们想想,二维数组名就是数组首行元素的地址,第一行的一维数组类型就是int [5],第一行的地址类型就行int (*) [5],那么意味着二维数组传参的本质也是传递了地址,传的是第一行这个一位数组的地址,形参也可以写成指针形式,如下:
#include<stdio.h>void test(int (*p)[5], int r, int c)
{int i = 0;int j = 0;for(i = 0;i < r; i++){for(j = 0; j < c; j++){printf("%d ",*(*(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}};test(arr, 3, 5);return 0;
}
3. 函数指针数组(应用:转移表)
3.1 函数指针数组定义
数组是一个存放相同数据类型的存储空间,把几个函数的地址存放到一个数组中,这个数组就叫函数指针数组。嗯,函数指针数组,听起来好复杂,它又应该怎么定义呢?
int (*parr[num]) ();
解释一下,变量名是parr,先与[num]结合表明这是一个数组,去掉数组部分,剩余部分是数组元素类型,剩余部分是int(*) (),很明显类型是函数指针,所以这是一个元素类型为函数指针的数组。这样是不是有所理解了呢。
3.2 转移表
举例:计算器加减乘除的实现
#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("* 0.exit *\n");printf("*3.mul 4.div*\n");printf("***********************\n");printf("请选择:>\n");scanf("%d", &input);switch (input){case 1:printf("输⼊操作数:>\n");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;
}
上面代码没有处理除数为0的情况,而且代码非常冗余,下面这一段代码重复了多次,只有选择函数部分不一致,如果我们加减乘除函数存放在数组中,通过数组选择加减乘除函数,是不是大大减少了代码量呢?下面我们实现一下。
printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = func(x, y);printf("ret = %d\n", ret);break;
使用函数指针数组实现:
//转移表实现计算器加减乘除(使用函数指针数组)
#include<stdio.h>void menu()
{printf("***********************\n");printf("*1.add 2.sub*\n");printf("* 0.exit *\n");printf("*3.mul 4.div*\n");printf("***********************\n");
}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 = 0;int y = 0;int input = 1;int (*p[5])(int x, int y) = { NULL, add, sub, mul, div };do{menu();printf("请选择:>\n");scanf("%d", &input);//判断有效性if (input >= 1 && input <= 4){printf("请输入操作数:>\n");scanf("%d %d", &x, &y);//判断除数为0的情况if (input == 4 && y == 0){printf("除数不能为0\n");continue;}int ret = (*p[input])(x, y);printf("%d\n", ret);}else if (input == 0){printf("退出计算器\n");break;}else{printf("输入有误,请重新输入\n");}} while (input);return 0;
}
4. 回调函数
4.1回调函数定义
回调函数就是通过函数指针调用的函数。
当我们将一个函数的指针(地址)作为参数传递给另一个函数时,如果该指针被用于调用其指向的函数,这种被间接调用的函数就称为回调函数。回调函数的独特之处在于:它不是由函数定义方直接调用,而是在特定事件或条件发生时由第三方触发,用于响应相应的事件或条件。
4.2回调函数的应用(加减乘除计算器优化)
在上面第三节中我们实现加减乘除计算器时,可不可以用回调函数实现呢,封装一个函数,把加减乘除函数的地址作为参数传给函数,下面编程实现一下。
#include <stdio.h>void menu()
{printf("***********************\n");printf("*1.add 2.sub*\n");printf("* 0.exit *\n");printf("*3.mul 4.div*\n");printf("***********************\n");
}
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 func(int (*p)(int,int))
{int x = 0;int y = 0;int z = 0;printf("输⼊操作数:>\n");scanf("%d %d", &x, &y);z = (*p)(x,y);
printf("%d\n", z);
}
int main()
{int input = 1;int ret = 0;do{menu();printf("请选择:>\n");scanf("%d", &input);switch (input){case 1:func(add);break;case 2:func(sub);break;case 3:func(mul);break;case 4:func(div);break;case 0:printf("退出程序\n");break;default:printf("选择错误\n");break;}} while (input);return 0;
}
5. qsort使用举例及模拟实现(冒泡排序)
qsort是C语言中用于排序各种同类型数据的库函数,使用者需要实现一个比较函数,传入待比较的两个参数,并返回一个整型数据。(返回值>0,前者>后者;返回值<0,前者<后者;返回值=0,前者=后者)
5.1 qsort使用举例
5.1.1 使用qsort排序整型数据
//使用qsort排序整形
#include<stdio.h>
;
int int_cmp(const void* p1, const void* p2)
{return *(int*)p1 - *(int*)p2;
}int main()
{int arr[] = { 0,5,4,2,3,1,6,7,8,9 };qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++){printf("%d ", arr[i]);}return 0;
}
5.1.2 使用qsort排序结构体
//使用qsort排序结构体
#include<stdio.h>
#include<string.h>struct Person
{char name[20];int age;
};void print(struct Person p[])
{for (int i = 0; i < 3; i++){printf("%s %d \n", p[i].name, p[i].age);}
}//按照年龄排序
int stu_cmp_by_age(const void* p1, const void* p2)
{return ((struct Person*)p1)->age - ((struct Person*)p2)->age;
}
//按照年龄排序
void test1()
{struct Person s[] = { {"zhangsan", 21}, {"lisi", 25}, {"wangwu", 29} };int sz = sizeof(s) / sizeof(s[0]);qsort(s, sz, sizeof(s[0]), stu_cmp_by_age);print(s);
}//按照名字排序
int stu_cmp_by_name(const void* p1, const void* p2)
{return strcmp(((struct Person*)p1)->name, ((struct Person*)p2)->name);
}//按照名字排序
void test2()
{struct Person s[] = { {"zhangsan", 21}, {"lisi", 25}, {"wangwu", 29} };int sz = sizeof(s) / sizeof(s[0]);qsort(s, sz, sizeof(s[0]), stu_cmp_by_name);print(s);
}int main()
{test1();test2();return 0;
}
5.2 qsort的模拟实现(冒泡排序)
在指针篇1中我们学习了冒泡排序,这次我们使用冒泡排序的方法模拟实现一下qsort库函数。因为qsort不知道使用者要比较的是什么类型数据,所以传入的参数地址要使用void*类型(void*类型在指针篇2.3.3中提到)
//qsort模拟实现(使用冒泡排序)
#include<stdio.h>int int_cmp(const void* p1, const void* p2)
{return *(int*)p1 - *(int*)p2;
}void Swap(char* one, char* two, size_t width)
{for (int i = 0; i < width; i++){char tmp = *one;*one = *two;*two = tmp;one++;two++;}
}void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void*,const void*))
{for (int i = 0; i < sz - 1; i++){for (int 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 main()
{int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };int i = 0;bubble_sort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++){printf("%d ", arr[i]);}printf("\n");return 0;
}
6. sizeof和strlen的对比
6.1 sizeof
sizeof是C语言中的一个单目操作符,sizeof是计算变量所占内存空间大小的,单位是字节,如果操作数是数据类型的话,计算的是使用此类型创建的变量所占内存空间的大小。代码示例:
#inculde <stdio.h>int main()
{int a = 10;printf("%d\n", sizeof(a));printf("%d\n", sizeof a);printf("%d\n", sizeof(int));//都可以正常输出4return 0;
}
6.2 strlen
strlen是C语言库函数,使用需要包含头文件string.h,功能是求字符串长度。函数原型:
size_t strlen ( const char * str );
统计的是传入参数的地址到遇到 \0 之前字符串中字符的个数,strlen会一直向后找直到找到 \0 为止,所以可能存在越界查找。
#include <stdio.h>int main()
{char arr1[3] = {'a', 'b', 'c'};char arr2[] = "abc";printf("%d\n", strlen(arr1));//随机值printf("%d\n", strlen(arr2));//3printf("%d\n", sizeof(arr1));//3printf("%d\n", sizeof(arr2));//4return 0;
}