山东省东营市建设局网站,WordPress主题1002无标题,vc 做网站源码,o2o电商平台系统写在前面
在学习C语言过程#xff0c;总有一个要点难点离不开#xff0c;那就是大名鼎鼎的C语言指针#xff0c;也是应为有指针的存在#xff0c;使得C语言一直长盛不衰。因此不才把指针所学的所有功力都转换成这个笔记。希望对您有帮助#x1f970;#x1f970;
学习指…写在前面
在学习C语言过程总有一个要点难点离不开那就是大名鼎鼎的C语言指针也是应为有指针的存在使得C语言一直长盛不衰。因此不才把指针所学的所有功力都转换成这个笔记。希望对您有帮助
学习指针之前我们必须先精通数组因为在数组中可以学到C语言的精髓当前变量的数据类型是什么。这里给大家推荐一下不才写的数组笔记【C语言】数组https://blog.csdn.net/m0_71580879/article/details/138375047 目录
写在前面
一、指针的创建
二、指针是什么
三、指针变量是什么
四、指针类型
五、指针的解引用
六、野指针
6.1. 指针未初始化
6.2.指针越界访问
6.3.指针指向的空间释放
七、指针运算
7.1.指针- 整数
7.2.指针-指针
7.3.指针的关系运算
八、指针和数组
九、二级指针
9.1二级指针的运算
十、字符指针
十一、指针与数组的各种关系
11.1.指针数组
11.2数组指针
11.3.数组参数、指针参数
十二、函数指针
十三、函数指针数组
十四、指向函数指针数组的指针
十五、回调函数
十六、void*类型 一、指针的创建 指针的定义方式是 type * 在深入学习指针之前我们需要知道如何创建一个指针指针的关键字是什么。
举个栗子
#include stdio.hint main() {int a 1;int* p a;return 0;
}
解析
在上面代码中我们首先定义了一个 整形变量a之后我们就定义了一个 指针变量 并且把 a的地址赋值给了 整形指针变量p
解析 int * p a 在不才之前的笔记中就有写道 * 是解引用操作符【C语言】基础操作符大全但是在此时 * 变成了一个标识符标识了变量p 是一个指针变量。前面的 int 标志着 赋值的变量 类型是int整形这个知识极其重要。因为指针变量只用来存放地址的变量。存放在指针变量中的值都被当成地址处理所以必须把a的地址取出来赋值给指针变量。 二、指针是什么
在计算机科学中指针Pointer是编程语言中的一个对象利用地址它的值直接指向 points to存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元可以说地址指向该变量单元。因此将地址形象化的称为“指针”。意思是通过它能找到以它为地址 的内存单元。 那我们就可以这样理解指针 就是 内存内存 就是 指针。 三、指针变量是什么 指针变量是个变量用来存放内存单元的地址存放在指针中的值都被当成地址处理 既然我们知道了指针变量是变量那么指针变量大小是多少
如何计算我们的指针变量大小关键在于我们机器上的地址线。因为在 二、指针是什么 中我们了解到地址就是指针那么地址线的大小就关系着我们指针的大小。我们在2024年了现在的机器绝大部分都是32位 / 64位机器了32位机器就对于着32个地址线我们以32位机器为例
在32个地址中我们就有32根地址线那么假设每根地址线在寻址的是产生一个电信号正电/负电1或 者0。
那么32根地址线产生的地址就会是
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
......
11111111 11111111 11111111 11111111
每一个地址线占用空间大小是一个比特那么32个地址线就占用了32个比特。我们知道一个字节就是 八个比特那么32机器的指针变量占用空间就是 4个字节
通过上述的步骤我们不难计算出 在64位机器或环境中 指针变量 的大小是 8个字节 四、指针类型
指针中也有类型不过指针的具体类型需要根据 赋值变量 来决定如在 一、指针的创建 中讲诉的一样 //列举部分 char *pc NULL; int *pi NULL; short *ps NULL; long *pl NULL; float *pf NULL; double *pd NULL; 指针类型的具体作用在于可以让我们知道 指针 1 是跳过多少个字节也可以知道对指针解引用的时候有多大的权限能操作几个字节这点是指针的精髓处之一。 五、指针的解引用
我们已经知道了指针变量保存的是指针指向变量的地址。那么我们可以通过解引用来访问到变量的内容。
int main() {int i 100;int* a i;*a 50;printf(%d\n, *a);printf(%d\n, i);return 0;
}在上面截图中我们不难发现i变量的值进行了改变那么如果想通过指针变量改变指向的变量的值我们只需要使用解引用操作符就可以完成相对于的操作。*a就等于i 结合四、五的复合例子设为小端字节序
#include stdio.h
int main()
{int n 0x11223344;char* pc (char*)n;int* pi n;*pc 0;*pi 0;return 0;
}
解析
在小端字节序中我们n变量在内存中的存储为 在上图中我们可以看到我们n变量的值低位存放在低地址处高位存放在高地址处。
接着我们把n变量强制类型转换成 char* 后赋值给指针变量pc 此时我们变量pc就指向了n变量的地址。也把n变量的指针赋值给了int* pi。
我们解引用pc把0赋值给*pc即把0赋值给整形变量n我们知道char类型只能访问一个字节那么在内内存中我们指针变量pc也只能访问到一个字节并把0赋值给第一个字节。 在调试内存中我们也清晰看见整形变量 n 四个字节中的第一个字节被修改成00其他字节不改变这说明了指针变量解引用后也收到我们类型的约束类型可以访问多少字节类型指针也只能访问多少字节。
然后程序接着进行把整形指针变量解引用*pi并且把0赋值给*pi。
整形在内存中可以访问4个字节整形指针也同理解引用后也只能访问四个字节我们把0赋值给了*pi那再内存中我们就把n变量的四个字节全部赋值为0 六、野指针 概念 野指针就是指针指向的位置是不可知的随机的、不正确的、没有明确限制的指针变量 在定义时如果未初始化其值是随机的指针变量的值是别的变量的地址意味着指针指向了一 个地址是不确定的变量此时去解引用就是去访问了一个不确定的地址所以结果是不可知的。 野指针成因
1. 指针未初始化2. 指针越界访问 3. 指针指向的空间释放 6.1. 指针未初始化
#include stdio.h
int main()
{int* p;//局部变量指针未初始化默认为随机值*p 20;return 0;
} 6.2.指针越界访问
#include stdio.hint main()
{int arr[10] { 0 };int* p arr;int i 0;for (i 0; i 11; i){//当指针指向的范围超出数组arr的范围时p就是野指针*(p) i;}return 0;
} 6.3.指针指向的空间释放
#include stdio.h
#include stdlib.hint main() {int* ps (int*)malloc(sizeof(int) * 10);if (ps NULL) {perror(main::ps);}*(ps 0) 100;free(ps);//在空间释放后ps为定义为NULL此时ps就为野指针return 0;
}
虽然在释放malloc内存后程序就结束了但是没有给ps指针赋值为NULLps是实打实的是野指针的若后续代码继续访问ps指针便成了野指针的访问。野指针的访问在程序编译链接中不会报错但是在程序运行中会输出不是预期值
规避野指针方法
1. 指针初始化 2. 小心指针越界 3. 指针指向空间释放即使置NULL 4. 指针使用之前检查有效性 七、指针运算
指针- 整数指针-指针指针的关系运算 7.1.指针- 整数 在上面四、五中讲解非常清晰这里不过多介绍可以上拉看四、五。 7.2.指针-指针 指针-指针的使用方式必须在连续空间上使用 指针-指针的作用用于返回连续空间的大小 指针-指针的值返回的是结尾指针到开头指针之间有多少个类型变量 举个栗子
#include stdio.hint main() {int arr[10] {1,2,3,4,5,6,7,8,9,\0};int* s arr;int* p NULL;int i 0;while(arr[i] ! \0){i;}p (arr i);printf(%d\n, p - s); //结果为9return 0;
}
在上面循环中已经把i的数记录了到 \0 前的元素个数后把 arr i 赋值给p那么p就指向了数组下标为 8 的地址p指针 - s指针“得到的结果为 9 说明arr数组在 \0 前里面存放了9个整形元素。 7.3.指针的关系运算 允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较但是不允许 与指向第一个元素之前的那个内存位置的指针进行比较。 举个栗子 数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较 int main() {char arr[10] 123456789;char* s arr;char* p arr 1;printf(%d\n, p - s);return 0;
}
我们知道arr 1是跳过整个数组那此时 指针p 指向的就是数组最后一个元素的后一位的地址我们进行指针 - 指针得到的结果为10。没有程序没有报错 数组元素的指针与指向第一个元素之前的那个内存位置的指针比较 int main() {char arr[10] 123456789;char* s arr - 1;char* p (arr 9);printf(%d\n, p - s);return 0;
}
实际在绝大部分的编译器上是可以顺利完成任务的然而我们还是应该避免这样写因为标准并不保证 它可行。 八、指针和数组 数组名是首元素地址 举个栗子
int main()
{int arr[10] { 1,2,3,4,5,6,7,8,9,0 };printf(%p\n, arr);printf(%p\n, arr[0]);return 0;
}运行结果%p 是打印地址 可见数组名和数组首元素的地址是一样的。
既然可以把数组名当成地址存放到一个指针中我们使用指针来访问一个数组就成为可能。
举个栗子
int main()
{int arr[] { 1,2,3,4,5,6,7,8,9,0 };int* p arr; //指针存放数组首元素的地址int sz sizeof(arr) / sizeof(arr[0]);int i 0;for (i 0; i sz; i){printf(arr[%d] %p p%d %p\n, i, arr[i], i, p i);}return 0;
}
运行结果为 所以pi其实计算的是数组 arr 下标为i的地址。 那我们就可以直接通过指针来访问数组。
那我们就可以直接通过指针来访问数组。 如下
int main()
{int arr[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };int* p arr; //指针存放数组首元素的地址int sz sizeof(arr) / sizeof(arr[0]);int i 0;for (i 0; i sz; i){printf(%d , *(p i));}return 0;
}
此时我们就得到一个重要结论:
arr[0] (p 0) *arr[0] *(p 0) arr[0] p[0] arr[0]
说明下标引用操作符 是与 指针加x解引用是相等的其实在底层运算中下标引用操作符是会转换成 指针加x解引用 的方式进行数据的访问的。
九、二级指针 指针变量也是变量是变量就有地址那指针变量的地址存放在哪里 这就是 二级指针。 举个栗子
int main() {int a 0;int* pa a;int** ppa pa;return 0;
}
说白了就是我们在指针创建的时候说的
解析 int * p a 在不才之前的笔记中就有写道 * 是解引用操作符【C语言】基础操作符大全但是在此时 * 变成了一个标识符标识了变量p 是一个指针变量。前面的 int 标志着 赋值的变量 类型是int整形这个知识极其重要。因为指针变量只用来存放地址的变量。存放在指针变量中的值都被当成地址处理所以必须把a的地址取出来赋值给指针变量。的例子
我们知道了a的类型是int所以把指针变量设置为了int类型指针同理我们知道我们要赋值给指针变量 ppa 的数据类型是一级指针类型int*那么我们定义的类型也是int*我们也要再加上一个 * 用来表示变量 n 是指针变量int* *ppa)。下图表示各级指针在内存存储图 9.1二级指针的运算
举个栗子
int main() {int a 0;int* pa a;int** ppa pa;return 0;
}
*ppa 通过对ppa中的地址进行解引用这样找到的是 int b 20; *ppa b;//等价于 pa b; pa *ppa 其实访问的就是 pa 。如下
int b 20;*ppa b;//等价于 pa b;
**ppa 先通过 *ppa 找到 pa ,然后对pa进行解引用操作 **ppa 30; //等价于*pa 30; //等价于a 30; *pa 那找到的是a。如下
**ppa 30;//等价于*pa 30;//等价于a 30;
十、字符指针 在指针的类型中我们知道有一种指针类型为字符指针 一般使用: int main()
{char ch w;char* pc ch;*pc w;return 0;
}
还有一种使用方式如下
int main()
{char* pstr hello bit.;//这里是把一个字符串放到pstr指针变量里了吗printf(%s\n, pstr);return 0;
}
这里就涉及到了常量字符串的内容常量字符串是不能被改变的只会提供首元素地址。不才在之前有写过一篇专门讲字符串的笔记大家可以阅读一下更利于理解字符指针的内容【C语言】字符串https://blog.csdn.net/m0_71580879/article/details/127864752
在理解了两种用法后可以尝试做一下这道题目
#include stdio.h
int main()
{char str1[] hello bit.;char str2[] hello bit.;char* str3 hello bit.;char* str4 hello bit.;if (str1 str2)printf(str1 and str2 are same\n);elseprintf(str1 and str2 are not same\n);if (str3 str4)printf(str3 and str4 are same\n);elseprintf(str3 and str4 are not same\n);return 0;}运行结果 解析
这里str3和str4指向的是一个同一个常量字符串。C/C会把常量字符串存储到单独的一个内存区域 当几个指针指向同一个字符串的时候他们实际会指向同一块内存。但是在数组中用相同的常量字符串去初始化不同的数组就会开辟出不同的内存块来存储常量字符串的内容。所以str1和str2不同str3和str4不同。 十一、指针与数组的各种关系
这小节讲的是指针与数组的各种关系如对数组没有太深入的了解的话没看着小结会很吃力。这里推荐先看不才完的数组笔记后再回来复习这小结 【C语言】数组https://blog.csdn.net/m0_71580879/article/details/138375047
11.1.指针数组 指针数组是指针还是数组 答案是数组。 是存放指针的数组。 数组我们已经知道整形数组字符数组那自然是有指针数组 我们先了解一下整形数组在内存中的布局和字符数组在内存中的布局 我们可以清晰的观察到数组中连续空间的类型同理指针数组也是一样
举个栗子 char* arr3[5]; 解析 char* arr3[5]
首先根据优先级arr3会与 [] 结合形成一个数组数组里面有5个元素 - arr3[5]数组存放的元素类型是 char*所以 char* arr3[5] 就定义了 可以存放5个元素每个元素类型是char* 的 指针数组 11.2数组指针 数组指针指向数组的指针 我们已经熟悉 整形指针 int * pint; 能够指向整形数据的指针。 浮点型指针 float * pf; 能够指向浮点型数据的指针。
那数组指针应该是能够指向数组的指针。
举个栗子int (*p2)[5];
根据优先级 变量p2先与括号内的*标识符结合*标识符表示了p2变量是一个指针变量出了括号后根据优先级马上与 [] 数组标识符结合 (*p2)[] 表示了指针变量p2指向了一个数组(*p2)[5]表示p2指向的数组中有5个元素。int (*p2)[5]表示p2指向的数组的5个元素每个元素都是int类型。
这里要注意[]的优先级要高于*号的所以必须加上来保证p先和*结合。 数组指针的使用 既然数组指针指向的是数组那数组指针中存放的应该是数组的地址。
举个栗子
int main()
{int arr[10] { 1,2,3,4,5,6,7,8,9,0 };int(*p)[10] arr;//把数组arr的地址赋值给数组指针变量p//但是我们一般很少这样写代码return 0;
}这样数组指针就可以访问到数组的元素但是我们一般不这样用一般把数组指针用在函数的形参中。
举个栗子
#include stdio.h
void print_arr1(int arr[3][5], int row, int col)
{int i 0;for (i 0; i row; i){for (j 0; j col; j){printf(%d , arr[i][j]);}printf(\n);}
}
void print_arr2(int(*arr)[5], int row, int col) //在形参中使用数组指针
{int i 0;for (i 0; i row; i){for (j 0; j col; j){printf(%d , arr[i][j]);}printf(\n);}
}
int main()
{int arr[3][5] { 1,2,3,4,5,6,7,8,9,10 };print_arr1(arr, 3, 5);//数组名arr表示首元素的地址//但是二维数组的首元素是二维数组的第一行//所以这里传递的arr其实相当于第一行的地址是一维数组的地址//可以数组指针来接收print_arr2(arr, 3, 5);return 0;
}
在上面代码 print_arr2 函数中我们使用数组指针接收了实参 传来的二维数组的首元素地址即一个一维数组的地址使用数组指针遍历了二维数组的全部元素。
复习了上面两个小点我们触类旁通一下下面代码的意思
int arr[5];
int* parr1[10];
int(*parr2)[10];
int(*parr3[6])[5];
解析
第一个以存放5个元素且每个元素是int类型的一维数组第二个是一个可以存放10元素且每个元素是int* 类型的 指针数组第三个是一个 指向可以存放10元素且每个元素是int 类型的数组 的数组指针第四个是一个可以存放6元素且每个元素是 int(*)[5] 数组指针类型的 指针数组
我们单独画出 int(*parr3[6])[5] 的内存分布图 11.3.数组参数、指针参数 一维数组传参我们形参可以使用一位数组、一级指针、数组指针或二维数组来接收 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);
} 在上面几个函数形参都是可以定义的
解析由上到下依此为12345
正确。实参是整形数组形参使用整形数组来接收时没有问题的。同上。正确。我们知道在数组中只有两种特殊情况数组名代表着整个数组的地址那我们观察可以知 arr数组的全部元素都是 int类型所以使用 int类型指针来接收首元素地址时可行的。正确。实参是指针数组形参使用实参数组来接收时没有问题当然对数组了解的足够深入的话就知道此时arr2数组的类型时 int* [20] 在形参中使用相同类型 int* [20] 来接收自然时没有问题的。 正确。实参传递了首元素地址实参的每个元素类型是整形一级指针 int* 所以在形参上使用整形二级指针来接收时可行的。 二维数组传参 通过了一维数组传参的例子后二维数组传参也是异曲同工 void test(int arr[3][5])//ok
{}
void test(int arr[][])//ok
{}
void test(int arr[][5])//ok
{}
void test(int* arr)//ok
{}
void test(int* arr[5])//ok
{}
void test(int(*arr)[5])//ok
{}
void test(int** arr)//ok
{}
int main()
{int arr[3][5] { 0 };test(arr);
}
解析由上到下依此为1234567
分析传参是否配对我们首先要分析我们实参的元素类型和首元素类型。在这个栗子中我们可以判断出每个元素的类型时 int 类型。首元素是二维数组第一行的地址那么首元素类型是 int [5] 是整形数组类型
正确。使用相同类型int [3][5]接收实参毫无问题错误。二位数组的问题只能省略创建多少行不能省略一行有多少个元素具体可看不才笔记【C语言】数组正确。实参首元素类型是 int [5] 形参类型使用二维数组每行可以接收5个int元素类型匹配。错误。实参首元素类型是 int [5] 形参是整形指针int*类型类型不匹配。错误。实参首元素类型是 int [5] 形参是每个元素为int*类型的指针数组类型类型不匹配。正确。实参首元素类型是 int [5] 形参是数组指针类型且指向的元素类型为 (int [5])类型匹配。错误实参首元素类型是 int [5] 形参是二级整形指针int**类型类型错误。 一级指针传参 以 void test1(int *p); 函数为例。
可以接收 int 类型参数的取地址 即int a 10; test1(a);可以接收 int* 类型参数即int* p a; test1(p);可以接收整形一维数组 即int arr[5] { 0 }; test1(arr); 二级指针传参 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?return 0;
}
解析由上到下依此为123
正确形参可以接收类型是 char* 等类型c取地址后的类型是 char* 类型匹配。正确形参可以接收类型是 char* 等类型ppc的类型是 char* 类型匹配。正确形参可以接收类型是 char* 等类型arr首元素类型是 char* ,类型匹配。
十二、函数指针 我们用过整形指针、字符指针、数组指针此时我们的函数的地址也可以使用指针获取的 首先我们先要知道函数的地址是怎么表达的
#include stdio.h
void test()
{printf(hehe\n);
}
int main()
{printf(%p\n, test);printf(%p\n, test);return 0;
}
运行结果 上面结果可知我们函数名就是函数地址函数名与取地址函数的到的结果是一样的。这里我们可以直接认为相同因为函数地址不会进行任何的算数运算因为函数地址进行算数运算没有任何意义。
我们得知函数的地址后我们则需要创建函数指针来保存函数的地址。
我们还是按照数组指针的创建思想来进行我们创建一个函数指针来接收上面例子的test函数
首先我们需要指针标识符和变量结合即加上括号 *ps把ps设为指针变量后我们需要结合函数标识符 () 这时我们就得到 (*ps)() 这样我们雏形的函数指针就设好了test函数有一个 int类型 形参所以我们在指针变量中也要加上所对应的形参即(*ps)(int),在函数指针的形参设定中不需要进行形参变量的定义的只需要标识类型即可。接着我们查看test函数的返回类型是什么就把所对应的返回类型填入函数指针中即void (*ps)(int) 。这样我们就把一个函数指针ps定义好啦。最后我们把函数 test 地址赋值给函数指针ps即可。void (*ps)(int) test;
经过上面步骤就可以完成函数指针的创建与初始化。 函数指针的使用 函数指针的使用和正常函数的使用方式是一样的。
为了更直观的感受不才在test函数上增加了打印变量
void test(int a)
{printf(%d\n, a);printf(hehe\n);
}
int main()
{void (*ps)(int) test;int n 110;ps(n);return 0;
}运行结果 那使用时一样的为什么我们还需要搞个函数指针呢
不才回复存在即有意义
这里我们就需要复习到函数指针数组
十三、函数指针数组 顾名思义函数指针数组就是用来存放函数指针的数组 函数指针数组的创建
结合优先级和十二小结创建函数指针来进行大胆推测。我们假设存放5个函数都为void a1(int a, int b)
我们已经知道了函数指针就是 type (* ps)(...)的模式创建因为括号的优先级高所以ps先与*结合成为指针变量我们只需要在括号内 加上数组标识符即可根据优先级把ps与数组标识符结合了即type (* ps[])(...)那么我们要存放我们的要求那就先创建数组 即 ps[5]。那么我们数组内的每个元素的类型都要求为 void (*)(int, int)那么结合起来 即 void (* ps[5])(int, int)
当然如果我们使用类型重定义typedef的话可以实现和 int类型 定义一样(int a) 左右布局来定义
例如
void test(int a, int b) {}int main() {typedef void (* nnt)(int, int);int x 0;int u 0;nnt a[2] { 0 };a[0] test;return 0;
} 不才把 void(*)(int.int) 类型重新命名为 nnt此时我们就可以使用nnt来进行类型定义了。 函数指针数组的用途转移表 我们在c语言中制作一个计算器在选择语句中需要使用switch语句其中需要使用到大量的case-break语句来进行语句的划分。
选择我们只需要使用函数指针数组转移表即可以简化多行代码实现选择语句。
例如我们使用先使用switch语句来进行计算机选择语句的逻辑条件
int main()
{int x, y;int input 1;int ret 0;do{printf(*************************\n);printf( 1:add 2:sub \n);printf( 3:mul 4:div \n);printf(*************************\n);printf(请选择);scanf(%d, input);switch (input){case 1:printf(输入操作数);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;
} 为了实现两个数的计算写了大量的case-break代码且case中代码高耦合只有在函数不同。
现在我们使用转移表来实现低耦合
int main()
{int x, y;int input 1;int ret 0;int(*p[5])(int x, int y) { 0, add, sub, mul, div }; //转移表while (input){printf(*************************\n);printf( 1:add 2:sub \n);printf( 3:mul 4:div \n);printf(*************************\n);printf(请选择);scanf(%d, input);if ((input 4 input 1)){printf(输入操作数);scanf(%d %d, x, y);ret (*p[input])(x, y);}elseprintf(输入有误\n);printf(ret %d\n, ret);}return 0;
}
不才只写了一个判断语句根据输入不同数字找到对于的数组下标访问对应的函数。这样大大减少了代码量而且还把耦合降低了避免了大量的相同代码。 十四、指向函数指针数组的指针 指向函数指针数组的指针 是一个 指针 指针指向一个 数组数组的元素都是函数指针 其实无非就是类型数组指针只不过这个数组的元素类型都是函数指针。
相同的定义过程还是以 void (* ps[5])(int, int)为例改造成一个指向5个元素数组且每个元素时函数指针 的指针
在函数指针数组的基础上把变量优先级先与指针标识符绑定。即 *ps那么其他照旧 void (* *ps[5])(int, int)这样即表示了ps 时一个指针变量根据优先级指针变量ps先和数组标识符 [] 结合那么他指向的就是一个数组数组里面有五个元素每个元素的类型是 void(*)(int, int) 。 异曲同工的我们可以继续无限的推理出 函数指针数组的指针的数组、指向函数指针数组的指针的数组的指针...这里不才就不过多介绍了。大伙有兴趣可以自己推理玩一下 十五、回调函数 回调函数就是一个通过函数指针调用的函数。如果你把函数的指针地址作为参数传递给另一 个函数当这个指针被用来调用其所指向的函数时我们就说这是回调函数。回调函数不是由该 函数的实现方直接调用而是在特定的事件或条件发生时由另外的一方调用的用于对该事件或 条件进行响应。 我们使用一下qsort函数来体验一下回调函数是什么。
#include stdio.h
//qosrt函数的使用者得实现一个比较函数
int int_cmp(const void* p1, const void* p2)
{return (*(int*)p1 - *(int*)p2);
}
int main()
{int arr[] { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };int i 0;qsort(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;
}
使用qsort函数时我们需要自己创建一个比较函数然后把我们写好的比较函数的地址以形参的形式传输到qsort中给qsort确认如何比较。这样我们的比较函数int_cmp函数就形成了回调函数。
这里时官方文档连接点我跳转qsort官方文档。 在阅读文档时候我们发现qsort函数使用的形参居然时( void* )类型。什么是void*类型呢
十六、void*类型 void* 类型是无类型指针用于不知道实参是什么类型的时候可以把形参定义为void*类型void*类型可以接收所有类型在使用变量时候我们自己强制类型转换为我们需要类型即可但是一般是强转换成char*并要求使用时提供一个元素大小时是多少字节在提供一个元素多少字节后使用char* 循环范围多少个字节空间即可得到一个元素的内容。 #include stdio.h
int int_cmp(const void* p1, const void* p2)//确保与bubble函数调用时一致所以定义为void*
{return (*(int*)p1 - *(int*)p2);//程序猿自己创建的函数,知道时整形比较
}
void _swap(void* p1, void* p2, int size)//交换函数
{int i 0;for (i 0; i size; i)//传入了一个元素的大小循环size次即可把元素交换成功{char tmp *((char*)p1 i);*((char*)p1 i) *((char*)p2 i);//一个字节一个字节的交换*((char*)p2 i) tmp;}
}
void bubble(void* base, int count, int size, int(*cmp)(void*, void*))//普通冒泡排序
{int i 0;int j 0;for (i 0; i count - 1; i){for (j 0; j count - i - 1; j){if (cmp((char*)base j * size, (char*)base (j 1) * size) 0)//(char*)base * size可以计算出一个元素的占用字节。{_swap((char*)base j * size, (char*)base (j 1) * size, size);}}}
}
int main()
{int arr[] { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };int i 0;bubble(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;
} 以上就是本章所有内容。若有勘误请私信不才。万分感激 若有帮助不要吝啬点赞哟~~
ps:表情包来自网络侵删
若是看了这篇笔记彻底拿捏指针可以在评论区打出小小指针拿捏