目录
指针是什么
野指针
字符指针
指针数组
数组指针
&数组名和数组名
数组指针的使用
数组参数、指针参数
一维数组传参
二维数组传参
一级指针传参
二级指针传参
函数指针
函数指针数组
回调函数
qsort函数的使用
模拟实现qsort函数
结语
指针是什么
指针理解的2个要点:
1. 指针是内存中一个最小单元的编号,也就是地址。
2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量。
也就是是说,指针是变量,用来存放地址的,也就是内存。
简单来说的指针: 1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。4. 指针运算。
文章图片
(*)这个像乘号的符号在这里是解引用的意思,NULL是空指针的意思。野指针
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
成因1:指针未初始化字符指针
文章图片
成因2:指针越界访问
文章图片
成因3:指向的空间释放
文章图片
char* 就是字符指针,用来存放字符的。 这里通过修改 *pc 和 *j 来改变了 ch 和 i 的内容。
文章图片
这里是不是感觉把整个字符串放进了字符指针中?其实只是把字符串的首字母放了进去,前面用const会更严谨一些。
再来看下面这段代码,可以看出指针变量会把相同的内容放在同一块地址中,而下面两行会创建两个数组,两个数组的首元素地址是不一样的,所以把地址打印出来就会得到下面的结果。
文章图片
指针数组
首先先理解一下这个名字,方便接下来的介绍。
比如:好老师。好是形容词,老师是名词,最终是老师。
那指针数组:存放的是指针,它是一个数组,最终是数组。
下面有两个例子:
文章图片
文章图片
用指针数组就可以模拟出一个二维数组
文章图片
如同创建了一个三行五列的二维数组
文章图片
这里也有不同的写法
文章图片
数组指针
按照规则: 这是指针,指向数组的
文章图片
之后要是判断都要通过这个解释,(*)的优先级要低于( [ ] ),所以要加上()&数组名和数组名
文章图片
虽然前两个的结果一样,通过加1我们可以知道,arr + 1跳过了一个元素,但是 &arr + 1 跳过了一整个数组。数组指针的使用
文章图片
数组参数、指针参数
在写代码的时候可能要把 [数组] 或者 [指针] 传给函数一维数组传参
文章图片
形参写成数组的形式:
文章图片
此处的形参的数值可写可不写。
形参写成指针的形式:二维数组传参
文章图片
无论是数组传参还是指针传参,传过去的都是数组首元素的地址,然后从首元素依次向后访问
一级指针传参
文章图片
形参写成数组的形式:
文章图片
这里二维数组的行可写可不写,只要不乱写,但是列必须写, 总之传过来的都是数组首元素的地址,对于二维数组来说,数组首元素的地址是二维数组第一行的地址。
所以也可以这样写:
文章图片
看下面这个例子:二级指针传参
文章图片
这里函数的参数部分是指针的形式,所以我们只要传一个地址过来就可以实现这个函数;
【c语言|C语言——指针】
文章图片
二级指针就是存放一级指针的指针函数指针
文章图片
这里函数的参数就是二级指针,所以要传入一个二级指针
文章图片
可以传入一个指针数组 ,因为数组中每个元素都是指针。
当然就是指向函数的指针函数指针数组
文章图片
既然是指针,那就会有地址,函数本身就是有地址的。
对于函数指针该怎么使用,()这个符号的意思是函数调用符号,所以既然使用函数指针,就要用(*p),要把Add这个函数存进(*p)中,函数参数有两个,都是int类型,最后函数的返回值是int。
文章图片
函数指针就可以这样用,这里说一下,这个(*p)的括号是不可以省略的,但是这个 * 是可以省略的,为了方便理解,最好还是把这个 * 加上。
文章图片
回调函数
文章图片
下面模拟了一个简单的计算器
文章图片
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.Add2.Sub ******\n"); printf("***** 3.Mul4.Div ******\n"); printf("******** 0.exit **********\n"); printf("**************************\n"); } int main() { int ret = 0; int input = 0; int x = 0; int y = 0; int (*pfArr[5])(int, int) = { 0, Add, Sub, Mul, Div }; do { menu(); printf("请选择\n"); scanf("%d", &input); if (input == 0) { printf("退出程序\n"); } else if (input >= 1 && input <= 4) { printf("请输入两个操作数\n"); scanf("%d%d", &x, &y); ret = pfArr[input](x, y); printf("ret = %d\n", ret); } else { printf("选择错误,请重新选择\n"); } } while (input); return 0; }
如果这里想写一个指向函数指针数组的指针该怎样去写呢?
文章图片
文章图片
文章图片
文章图片
文章图片
所以这样写也是可以的
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。 简单来说就是在函数中调用另一个函数。
qsort函数的使用void menu() { printf("**************************\n"); printf("***** 1.Add2.Sub ******\n"); printf("***** 3.Mul4.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 ret = 0; int x = 0; int y = 0; printf("请输入两个操作数:\n"); scanf("%d %d", &x, &y); ret = (*pf)(x, y); printf("%d\n", ret); }int main() { int input = 0; do { menu(); printf("请选择\n"); scanf("%d", &input); switch (input) { case 0: printf("退出程序\n"); break; case 1: calc(Add); break; case 2: calc(Sub); break; case 3: calc(Mul); break; case 4: calc(Div); break; default: printf("选择错误,请重新选择\n"); break; } } while (input); return 0; }
继续拿上面那个例子,在calc函数中调用各个函数,也就是回调函数的简单用法。
这个函数是需要引头文件的:#include
文章图片
模拟实现qsort函数#include void print_arr(int arr[], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } } int cmp_int(const void* e1, const void* e2) { return (*(int*)e1 - *(int*)e2); //这里要强制类型转换一下,因为void不知道是几个字节,排序什么样的数据就要强制类型转换成什么 } 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]), cmp_int); print_arr(arr, sz); } int main() { test(); return 0; }
文章图片
这个qsort函数默认是排升序的,如果想要排降序,只需要把e1 和 e2的位置换一下,也可以在cmp_int函数返回值前加一个符号,就如同:
文章图片
文章图片
结语void print_arr(int arr[], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } } int cmp_int(const void* e1, const void* e2) { return (*(int*)e1 - *(int*)e2); } 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 num, int width, int (*cmp)(const void* e1, const void* e2)) { int i = 0; for (i = 0; i < num - 1; i++) { int j = 0; for (j = 0; j < num - 1 - i; j++) {//cmp是一个函数指针,指向cmp_int if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)//排为升序,前面的大于后面的就交换,这里的解释下面有图 { //交换 Swap((char*)base + j * width, (char*)base + (j + 1) * width, width); } } } } void test() { 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); //这就是模拟的qsort函数 print_arr(arr, sz); //打印函数} int main() { test(); return 0; }
文章图片
文章图片
这里的模拟qsort函数使用冒泡排序的方法实现的,这只是一种实现方法,并不是唯一的方法。
对于C语言指针方面的内容就介绍完了,对于指针来说,让人又爱又恨,爱是它可以说是C语言的灵魂,恨是它真的比较难懂。不管怎样还是要把它学明白,这对我们程序员可是非常有帮助的。
推荐阅读
- 字符串操作|c语言——字符串右移
- c语言|C语言初阶——2.函数
- java|面试官(ZK(ZooKeeper)分布式锁实现,你了解了吗())
- 学习笔记|【Kafka|常用CLI】Topic管理
- EECS 4412 Data Mining
- python|Python 学生信息管理系统------文章中源码100%真实有效-----如何将类、初始化属性、模块、循环判断、静态方法等一系列知识点结合起来做一个项目
- 前端优秀开源项目|cnchar - 功能全面、支持多端的汉字拼音笔画开源 JS 库
- JavaWeb|基于Java开发的CMS内容管理系统
- scheme|scheme中define和set