指针万字超级最强i解析与总结!!!!!
文章目录
- 1.内存和地址
- 1.1内存
- 1.2究竟该如何理解编址
- 2.指针变量和地址
- 2.1 取地址操作符(&)
- 2.2指针变量和解引用操作符(*)
- 2.2.1指针变量
- 2.2.2如何拆解指针类型
- 2.2.3解引用操作符
- 2.3 指针变量的大小
- 3.指针变量类型的意义
- 3.1指针的解引用
- 3.2指针+-整数
- 3.3 void* 指针
- 4.指针运算
- 4.1指针+-整数
- 4.2指针-指针
- 4.3 指针的关系运算
- 5.const修饰指针
- 5.1 const修饰指针
- 5.2 const 修饰指针变量
- 6.野指针
- 6.1野指针成因
- 6.1.1指针未初始化
- 6.1.2 指针的越界访问
- 6.1.3指针指向的空间释放
- 6.2如何规避野指针
- 6.2.1指针初始化
- 6.2.2小心指针越界
- 6.2.3指针变量不再使用时,及时置NULL,指针使用之前检查有效性
- 6.2.4避免返回局部变量的地址
- 7.assert断言
- 8.指针的使用和传址调用
- 8.1 strlen的模拟实现
- 8.2传值调用和传址调用
- 9.数组名的理解
- 10.使用指针访问数组
- 11.一维数组传参的本质
- 12.冒泡排序
- 13.二级指针
- 14.指针数组
- 15.指针数组模拟二维数组
- 16.字符指针变量
- 17.数组指针变量
- 17.1数组指针变量是什么?
- 17.2数组指针变量怎么初始化
- 18.二维数组传参的本质
- 19.函数指针变量
- 19.1函数指针变量的创建
- 19.2函数指针变量的使用
- 19.3两段有趣的代码
- 19.3.1 typedef 关键字
- 20.函数指针数组
- 21.转移表
- 22.回调函数是什么?
- 23.qsort使用举例
- 23.1使用qsort函数排序整形数据
- 23.2使用sqort排序结构数据
- 24.qsort函数的模拟实现
1.内存和地址
1.1内存
我们知道计算机上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放回内存中,那我们买电脑的时候,电脑上内存是8GB/16GB/32GB等,那这些内存空间如何⾼效的管理呢?
其实也是把内存划分为⼀个个的内存单元,每个内存单元的⼤⼩取1个字节
⼀个⽐特位可以存储⼀个2进制的位1或者0
1byte = 8bit
1KB = 1024byte
1MB = 1024KB
1GB = 1024MB
1TB = 1024GB
1PB = 1024TB
1.2究竟该如何理解编址
CPU访问内存中的某个字节空间,必须知道这个字节空间在内存的什么位置,⽽因为内存中字节很多,所以需要给内存进⾏编址
计算机中的编址,并不是把每个字节的地址记录下来,⽽是通过硬件设计完成的
⾸先,必须理解,计算机内是有很多的硬件单元,⽽硬件单元是要互相协同⼯作的。所谓的协
同,⾄少相互之间要能够进⾏数据传递。但是硬件与硬件之间是互相独⽴的,那么如何通
信呢?答案很简单,⽤"线"连起来。⽽CPU和内存之间也是有⼤量的数据交互的,所
以,两者必须也⽤线连起来。
我们今天关⼼⼀组线,叫做地址总线。
32位机器有32根地址总线,每根线只有两态,表⽰0,1【电脉冲有⽆】,那么⼀根线,就能表⽰2种含义,2根线就能表⽰4种含义,依次类推。32根地址线,就能表⽰2^32种含义,每⼀种含义都代表⼀个地址。
地址信息被下达给内存,在内存上,就可以找到该地址对应的数据,将数据在通过数据总线传⼊CPU内寄存器
2.指针变量和地址
2.1 取地址操作符(&)
在C语⾔中创建变量其实就是向内存申请空间
上述的代码就是创建了整型变量a,内存中申请4个字节,⽤于存放整数10,其中每个字节都
有地址,上图中4个字节的地址分别是
0x006FFD70
0x006FFD71
0x006FFD72
0x006FFD73
2.2指针变量和解引用操作符(*)
2.2.1指针变量
那我们通过取地址操作符(&)拿到的地址是⼀个数值,⽐如:0x006FFD70,这个数值有时候也是需要存储起来,⽅便后期再使⽤的,那我们把这样的地址值存放在哪⾥呢?答案是:指针变量中
int* pa = &a;
//取出a的地址并存储到指针变量pa中
指针变量也是⼀种变量,这种变量就是⽤来存放地址的,存放在指针变量中的值都会理解为地址
2.2.2如何拆解指针类型
2.2.3解引用操作符
在现实⽣活中,我们使⽤地址要找到⼀个房间,在房间⾥可以拿去或者存放物品。
C语⾔中其实也是⼀样的,我们只要拿到了地址(指针),就可以通过地址(指针)找到地址(指针)指向的对象,这⾥必须学习⼀个操作符叫解引⽤操作符(*)
int a = 100;int* pa = &a;*pa = 0;
*pa的意思就是通过pa中存放的地址,找到指向的空间,*pa其实就是a变量了;所以 *pa=0,这个操作符是把a改成了0
2.3 指针变量的大小
前⾯的内容我们了解到,32位机器假设有32根地址总线,每根地址线出来的电信号转换成数字信号后
是1或者0,那我们把32根地址线产⽣的2进制序列当做⼀个地址,那么⼀个地址就是32个bit位,需要4个字节才能存储
同理64位机器,假设有64根地址线,⼀个地址就是64个⼆进制位组成的⼆进制序列,存储起来就需要
8个字节的空间,指针变量的⼤⼩就是8个字节。
3.指针变量类型的意义
3.1指针的解引用
3.2指针±整数
#include <stdio.h>
int main()
{
int n = 10;
char *pc = (char*)&n;
int *pi = &n;
printf("%p\n", &n);
printf("%p\n", pc);
printf("%p\n", pc+1);
printf("%p\n", pi);
printf("%p\n", pi+1);
return 0;
}
char* 类型的指针变量+1跳过1个字节,这就是指针变量的类型差异带来的变化。
int* 类型的指针变量+1跳过了4个字节
结论:指针的类型决定了指针向前或者向后⾛⼀步有多⼤(距离)
3.3 void* 指针
在指针类型中有⼀种特殊的类型是void* 类型的,可以理解为⽆具体类型的指针(或者叫泛型指
针),这种类型的指针可以⽤来接受任意类型地址。但是也有局限性,void* 类型的指针不能直接进⾏指针的±整数和解引⽤的运算。
#include <stdio.h>int main(){int a = 10;void* pa = &a;void* pc = &a;*pa = 10;*pc = 0;return 0;}
那么void* 类型的指针到底有什么⽤呢?⼀般void* 类型的指针是使用在函数参数的部分,⽤来接收同类型数据的地址,这样的设计可以实现泛型编程的效果,使得⼀个函数来处理多种类型的数据
4.指针运算
4.1指针±整数
#include <stdio.h>
int main()
{int arr[9] = {1, 2, 3, 4, 5, 6, 7, 8, 9};int* p = &arr[0];int sz = sizeof(arr) / sizeof(arr[0]);for (int i = 0;i < 9;i++){printf("%d ", *(p+i));}return 0;
}
4.2指针-指针
1.指针-指针时,两个指针指向同一块区域
2.指针-指针的绝对值,是两个指针之间的元素个数
#include <stdio.h>
int main()
{ int arr[6] = { 1,2,3,4,5,6 };int c = arr[5] - arr[0];printf("%d", c);//5return 0;
}
库函数strlen实现
#include <stdio.h>
size_t mystrlen(char* str)
{size_t* p = str;while (*str != '\0')str++;return str - p;
}
int main()
{ char arr[] = "abcdefj";size_t len = mystrlen(arr);printf("%zd", len);//7return 0;
}
4.3 指针的关系运算
两个地址可以比较大小
数组随着下标增长地址从低到高变化,
#include <stdio.h>
int main()
{ int arr[] = { 1,2,3,4,5 };int* p = arr;int sz = sizeof(arr) / sizeof(arr[0]);while (p < &arr[sz]){printf("%d ", *p);p++;}return 0;
}
5.const修饰指针
5.1 const修饰指针
变量是可以修改的,如果把变量的地址交给⼀个指针变量,通过指针变量的地址的改变也可以修改这个变量。
但是如果我们希望⼀个变量加上⼀些限制,不能被修改,怎么做呢?这就是const的作⽤
#include <stdio.h>int main(){int m = 0;m = 20;//m
是可以修改的
const int n = 0;n = 20;//n
是不能被修改的
return 0;}
但是如果我们绕过n,使⽤n的地址,去修改n就能做到了,虽然这样做是在打破语法规则
#include <stdio.h>
int main()
{
const int n = 0;
printf("n = %d\n", n);//0
int*p = &n;
*p = 20;
printf("n = %d\n", n);//20
return 0;
}
我们可以看到这⾥⼀个确实修改了,但是我们还是要思考⼀下,为什么n要被const修饰呢?就是为了
不能被修改,如果p拿到n的地址就能修改n,这样就打破了const的限制,这是不合理的,所以应该让
p拿到n的地址也不能修改n,那接下来怎么做呢
5.2 const 修饰指针变量
#include <stdio.h>
int main()
{ int num = 10;int m = 30;const int* p = #//num不能再改变了p = &m;printf("%d\n", *p);//30return 0;
}
const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。
但是指针变量本⾝的内容可变。
const如果放在*的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指
向的内容,可以通过指针改变
6.野指针
概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
理解野指针的时候,你可以把野指针想成“野狗”,是没有主⼈的,是危险的
6.1野指针成因
6.1.1指针未初始化
p没有指向空间
#include <stdio.h>int main(){
int *p;//
局部变量指针未初始化,默认为随机值
*p = 20;return 0;}
6.1.2 指针的越界访问
#include <stdio.h>
int main()
{ int arr[10] = { 0 };int* p = arr;for (int i = 0;i < 11;i++){*(p++) = 1;}return 0;
}
当指针指向的范围超过数组arr时,p就是野指针
6.1.3指针指向的空间释放
p得到地址的一瞬间就是野指针了
6.2如何规避野指针
6.2.1指针初始化
如果明确知道指针指向哪⾥就直接赋值地址,如果不知道指针应该指向哪,可以给指针赋值NULL.
NULL 是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是⽆法使⽤的,读写该地址
会报错
#include <stdio.h>int main(){int num = 10;int*p1 = #int* p2 = NULL;return 0;}
6.2.2小心指针越界
⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是
越界访问。
使⽤指针的时候⼀定要注意边界,通过指针访问的内存是不能越界的
例子如 2.1.2
6.2.3指针变量不再使用时,及时置NULL,指针使用之前检查有效性
当指针变量指向⼀块区域的时候,我们可以通过指针访问该区域,后期不再使⽤这个指针访问空间的时候,我们可以把该指针置为NULL。因为约定俗成的⼀个规则就是:只要是NULL指针就不去访问,
同时使⽤指针之前可以判断指针是否为NULL
#include <stdio.h>
int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};int *p = &arr[0];for(i=0; i<10; i++){*(p++) = i;}//
此时p已经越界了,可以把p置为NULLp = NULL;//
下次使⽤的时候,判断p不为NULL的时候再使⽤
//...p = &arr[0];//
重新让p获得地址
if(p != NULL) //判断
{//...}return 0;}
6.2.4避免返回局部变量的地址
如2.1.3 不要返回局部变量的地址。
7.assert断言
assert.h 头⽂件定义了宏assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报错终⽌运⾏。这个宏常常被称为“断⾔”
assert(p != NULL);
//验证p是否等于NULL,如果不等于就继续运行,否则停止运行并给出报错信息
assert() 宏接受⼀个表达式作为参数。
-
如果该表达式为真(返回值⾮零) assert()不会产⽣任何作⽤,程序继续运⾏
-
如果该表达式为假(返回值为零),assert() 就会报错,在标准错误流stderr 中写⼊⼀条错误信息,显⽰没有通过的表达式,以及包含这个表达式的⽂件名和⾏号
8.指针的使用和传址调用
8.1 strlen的模拟实现
#include <stdio.h>
#include <assert.h>
size_t my_strlen(const char* str)
{size_t count = 0;assert(str != NULL);while (*str != '\0'){count++;str++;}return count;
}
int main()
{char arr[] = "abcdef";size_t len = my_strlen(arr);printf("%zd", len);return 0;
}
8.2传值调用和传址调用
在函数调⽤的时候有传值调⽤,也有传址调⽤,那这两种调⽤⽅式有什么区别呢?
传址调⽤,就得使⽤指针;学习指针的⽬的是使⽤指针解决问题,那什么问题,⾮指针不可呢?
例如:写⼀个函数,交换两个整型变量的值
⼀番思考后,我们可能写出这样的代码
#include <stdio.h>void Swap1(int x, int y){int tmp = x;x = y;y = tmp;}int main(){int a = 0;int b = 0;scanf("%d %d", &a, &b);Swap1(a, b);printf("
交换前:
a=%d b=%d\n", a, b);printf("
交换后:
a=%d b=%d\n", a, b);return 0;}
结论:实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实
参。
所以Swap是失败的了
那怎么办呢?
我们现在要解决的就是当调⽤Swap函数的时候,Swap函数内部操作的就是main函数中的a和b,直接
将a和b的值交换了。那么就可以使⽤指针了,在main函数中将a和b的地址传递给Swap函数,Swap
函数⾥边通过地址间接的操作main函数中的a和b,并达到交换的效果就好了
#include <stdio.h>
void swap(int* a1, int* b1)
{int t = *a1;*a1 = *b1;*b1 = t;
}
int main()
{ int a = 10;int b = 0;swap(&a, &b);printf("%d %d", a, b);return 0;
}
这⾥调⽤swap函数的时候是将变量的地址传递给了函数,这种函数调⽤⽅式叫:传址调⽤
传址调⽤,可以让函数和主调函数之间建⽴真正的联系,在函数内部可以修改主调函数中的变量;所以未来函数中只是需要主调函数中的变量值来实现计算,就可以采⽤传值调⽤。如果函数内部要修改主调函数中的变量的值,就需要传址调⽤
9.数组名的理解
数组名是⾸元素的地址
#include <stdio.h>int main(){int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };printf("&arr[0] = %p\n", &arr[0]);printf("arr = %p\n", arr);
return 0;}
数组名如果是数组⾸元素的地址,那下⾯的代码怎么理解呢?
#include <stdio.h>
int main(){int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };printf("%d\n", sizeof(arr));return 0;}
输出的结果是:40,如果arr是数组⾸元素的地址,那输出应该的应该是4/8才对
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("&arr[0] = %p\n", &arr[0]);
printf("&arr[0]+1 = %p\n", &arr[0]+1);
printf("arr = %p\n", arr);
printf("arr+1 = %p\n", arr+1);
printf("&arr = %p\n", &arr);
printf("&arr+1 = %p\n", &arr+1);
return 0;
}
&arr[0] = 0077F820
&arr[0]+1 = 0077F824
arr = 0077F820
arr+1 = 0077F824
&arr = 0077F820
&arr+1 = 0077F848
这⾥我们发现&arr[0]和&arr[0]+1相差4个字节,arr和arr+1相差4个字节,是因为&arr[0]和arr都是
⾸元素的地址,+1就是跳过⼀个元素。但是&arr和&arr+1相差40个字节,这就是因为&arr是数组的地址,+1操作是跳过整个数组的。
到这⾥⼤家应该搞清楚数组名的意义了吧。
数组名是数组⾸元素的地址,但是有2个例外
10.使用指针访问数组
有了前⾯知识的⽀持,再结合数组的特点,我们就可以很⽅便的使⽤指针访问数组了
#include <stdio.h>
int main()
{ int arr[] = { 1,2,3,4,5 };int sz = sizeof(arr) / sizeof(arr[0]);int* p = arr;for (int i = 0;i < sz;i++){scanf("%d", p);p++;}p = arr;//重置p的位置for (int i = 0;i < sz;i++){printf("%d ", p[i]);}return 0;
}
p++操作,让指针挪远之后,记得重置p的位置
#include <stdio.h>
int main()
{ int arr[10] = { 0 };int* p = arr;int sz=sizeof(arr)/sizeof(arr[0]);for (int i = 0;i < sz;i++){scanf("%d", p + i);//scanf("%d",arr+i);}for (int i = 0;i < sz;i++){printf("%d ", *(p + i));}return 0;
}
将*(p+i)换成p[i]也是能够正常打印的,所以本质上p[i]是等价于*(p+i)。
同理arr[i] 应该等价于*(arr+i),数组元素的访问在编译器处理的时候,也是转换成⾸元素的地址+偏移
量求出元素的地址,然后解引⽤来访问的
11.一维数组传参的本质
当我们已经有了⼀维数组,我们对⼀维数组的处理是否可以使⽤函数呢?这样就涉及到将⼀维数组传
递给函数,就是⼀维数组传参。
⽐如:我们现在想写⼀个函数打印⼀个整型数组的内容。
#include <stdio.h>
void test(int arr[], int sz)
{for (int i=0;i<sz;i++){printf("%d ", arr[i]);}
}
int main()
{ int arr[] = { 1,2,3,4,5,6,7,8,9,10 };int sz = sizeof(arr) / sizeof(arr[0]);test(arr, sz);return 0;
}
我们前⾯是在函数外部计算数组的元素个数,那我们可以把函数传给⼀个函数后,函数内部求数组的元素个数吗
#include <stdio.h>
void test(int arr[], int sz)
{printf("%d", sizeof(arr) / sizeof(arr[0]));
}
int main()
{ int arr[] = { 1,2,3,4,5,6,7,8,9,10 };int sz = sizeof(arr) / sizeof(arr[0]);printf("%d\n", sz);test(arr, sz);return 0;
}
我们发现在函数内部是没有正确获得数组的元素个数
这就要学习数组传参的本质了,上个⼩节我们学习了:数组名是数组⾸元素的地址;
那么在数组传参的时候,传递的是数组名,也就是说本质上数组传参本质上传递的是数组⾸元素的地址。
所以函数形参的部分理论上应该使⽤指针变量来接收⾸元素的地址。
那么在函数内部我们写sizeof(arr) 计算的是⼀个地址的⼤⼩(单位字节)⽽不是数组的⼤⼩(单
位字节)。正是因为函数的参数部分是本质是指针,所以在函数内部是没办法求的数组元素个数的。
#include <stdio.h>
void test1(int arr[], int sz)//参数写成数组形式,本质上还是指针{printf("%d\n", sizeof(arr));
}
void test2(int* p, int sz)//参数写成指针形式{printf("%d", sizeof(p)); //计算⼀个指针变量的⼤⼩}
int main()
{ int arr[] = { 1,2,3,4,5,6,7,8,9,10 };int sz = sizeof(arr) / sizeof(arr[0]);test1(arr, sz);test(arr, sz);return 0;
}
总结:⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。
12.冒泡排序
#include <stdio.h>
void test1(int*arr,int sz)
{for (int i = 0;i < sz - 1;i++){for (int j = 0;j < sz - 1 - i;j++){if (arr[j] > arr[j + 1]){int t = arr[j];arr[j] = arr[j + 1];arr[j + 1] = t;}}}
}
int main()
{ int arr[] = { 9,8,7,5,4,6,3,2,10,1 };int sz = sizeof(arr) / sizeof(arr[0]);test1(arr, sz);for (int i = 0;i < sz ;i++){printf("%d ", arr[i]);}return 0;
}
优化
#include <stdio.h>
void test2(int*arr,int sz)
{for (int i = 0;i < sz - 1;i++){ int flag = 1;for (int j = 0;j < sz - 1 - i;j++){ if (arr[j] > arr[j + 1]){ int t = arr[j];arr[j] = arr[j + 1];arr[j + 1] = t;flag = 0;}}if (flag == 1)break;}
}
int main()
{ int arr[] = { 9,8,7,5,4,6,3,2,10,1 };int sz = sizeof(arr) / sizeof(arr[0]);test2(arr, sz);for (int i = 0;i < sz ;i++){printf("%d ", arr[i]);}return 0;
}
13.二级指针
#include <stdio.h>
int main()
{int a = 10;int b = 20;int* pa=&a;int const * * ppa=&pa;*ppa = &b;//等价pa=&bprintf("%d", **ppa);//20}
*pa解开是整形,*ppa解开是pa指针,**ppa解开也是整形 **ppa可看作*(*ppa)
#include <stdio.h>
int main()
{int a = 10;int b = 20;int* pa=&a;int ** ppa=&pa;**ppa = 30;//等价于*pa=30;//等价pa=30;printf("%d", **ppa);//30printf(
}
#include <stdio.h>
int main()
{int a = 10;int b = 20;int* pa=&a;int* pb = &b;int * const * ppa=&pa;ppa = &pb;printf("%d", **ppa);
}
可以使二级指针指向另外一个一级指针
哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈终于搞懂了
14.指针数组
指针数组是指针还是数组?
我们类⽐⼀下,整型数组,是存放整型的数组,字符数组是存放字符的数组。
那指针数组呢?是存放指针的数组。
15.指针数组模拟二维数组
#include <stdio.h>
int main()
{ int arr1[] = { 1,2,3,4,5 };int arr2[] = { 2,3,4,5,6 };int arr3[] = { 3,4,5,6,7 };int* parr[] = {arr1,arr2,arr3};for (int i = 0;i < 3;i++){for (int j = 0;j < 3;j++){printf("%d ", parr[i][j]);}printf("\n");}return 0;
}
arr[i]==*(arr+i)
arr[i][j]==x[j]==*(x+j)
==*(*(arr+1)+j)
16.字符指针变量
将字符串abcdef首元素a的地址给了p
字符数组可以放字符串,且字符数组可以修改如
arr[10]=“wasdrrr”;
const char*p=“abcedf”;
常量字符串和数组非常相似,也在一个连续空间存放了多个字符,但常量的内容不能修改,即*p='a’就错误,所以加了const
指针数组可以存字符串
17.数组指针变量
17.1数组指针变量是什么?
17.2数组指针变量怎么初始化
数组指针变量是⽤来存放数组地址的,那怎么获得数组的地址呢?就是我们之前学习的==&数组名==
int arr[10] = {0};int(*p)[10] = &arr;
18.二维数组传参的本质
过去我们有⼀个⼆维数组的需要传参给⼀个函数的时候,⽐如有⼀个整型的⼆维数组,写⼀个函数
test,打印数组的全部内容。我们可以这样写:
#include <stdio.h>
void test(int arr[3][5], int x, int y)
{for (int i = 0;i < 3;i++){for (int j = 0;j < 5;j++){printf("%d ", arr[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;
}
#include <stdio.h>
void test(int (*pa)[5], int x, int y)
{for (int i = 0;i < 3;i++){for (int j = 0;j < 5;j++){printf("%d ", pa[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;
}
总结:⼆维数组传参,形参的部分可以写成数组,也可以写成指针形式
19.函数指针变量
其实函数也是有地址的,我们做个测试:
19.1函数指针变量的创建
函数指针变量应该是⽤来存放函数地址的,未来通过地址能够调⽤函数的。
如果我们要将函数的地址存放起来,就得创建函数指针变量咯,函数指针变量的写法其实和数组指针
⾮常类似。如下
#include <stdio.h>
void Add(int x,int y)
{return x + y;
}
int main()
{ int (*p1)(int,int ) = Add;int (*p2)(int x, int y) = &Add;//x,y可不写return 0;
}
19.2函数指针变量的使用
#include <stdio.h>
void Add(int x,int y)
{return x + y;
}
int main()
{ int (*p1)(int,int ) = Add;printf("%d", p1(3, 5));//(*p1)return 0;
}
19.3两段有趣的代码
19.3.1 typedef 关键字
typedef void((*p_fun)(int));
//typedef int(*p_arr)[5];
p_fun signal(int, p_fun);
20.函数指针数组
#include <stdio.h>
int Add(int x,int y)
{return x + y;
}
int Sub(int x, int y)
{return x - y;
}
int main()
{int(*parr[])(int ,int) = {Add,Sub};printf("%d", parr[0](3, 1));
}
21.转移表
#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;
}
int main()
{int (*parr[])(int, int) = { NULL,Add,Sub,Div,Mul };int input;do{ int x, y;printf("******1.Add 2.Sub*******\n");printf("******3.Div 4.Mul*******\n");printf("*********0.退出游戏**********\n");scanf("%d", &input);if (0 == input){printf("退出游戏");break;}scanf("%d %d", &x, &y);int c=parr[input](x, y);printf("%d", c);} while (input);return 0;
}
这个函数数组称作转移表
22.回调函数是什么?
回调函数就是⼀个通过函数指针调⽤的函数。
如果你把函数的指针(地址)作为参数传递给另⼀个函数,当这个指针被⽤来调⽤其所指向的函数
时,被调⽤的函数就是回调函数。回调函数不是由该函数的实现⽅直接调⽤,⽽是在特定的事件或条
件发⽣时由另外的⼀⽅调⽤的,⽤于对该事件或条件进⾏响应
#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 cac(int (*pf)(int ,int) )//用所指向函数形式接收
{ int m, n;scanf("%d %d", &m, &n);int c=pf(m,n);printf("%d\n", c);
}
int main()
{int input;do {printf("****1.加法****\n");printf("****2.减法****\n");printf("****3.乘法****\n");printf("****4.除法****\n");printf("****0.退出****\n");scanf("%d", &input);switch (input){case 1:cac(Add);break;case 2:cac(Sub);break;case 3:cac(Mul);break;case 4:cac(Div);break;case 0:break;default:printf("输入错误,请重新输入");break;}} while (input);return 0;
}
23.qsort使用举例
23.1使用qsort函数排序整形数据
#include <stdio.h>
int paixu(const void* e1, const void* e2)
{return *(int*)e1 - *(int*)e2;
}
int main()
{ int arr[] = { 1,2,5,4,5,7,8,9,6,3 };qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(arr[0]), paixu);int i = 0;for (i = 0;i < sizeof(arr)/sizeof(arr[0]);i++){printf("%d ", arr[i]);}return 0;
}
23.2使用sqort排序结构数据
#include <stdio.h>
#include <stdlib.h>
struct stu
{char name[20];int age;
};
int pum1(const void* e1, const void* e2)
{return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name);
}
int main()
{ struct stu arr[3] = { {"zhangsan",15},{"lisi",19},{"wangwu",20} };qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(arr[0]), pum1);}
strcpm比较俩字符串大小,对应位置字符的ASCLL值。
按照年龄来排序,想从大到小排只需改变return两边的位置
#include <stdio.h>
#include <stdlib.h>
struct stu
{char name[20];int age;
};int pum2(const void* e1, const void* e2)
{return (*(struct stu*)e1).age - (*(struct stu*)e2).age;
}
int main()
{ struct stu arr[3] = { {"zhangsan",15},{"lisi",30},{"wangwu",20} };qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(arr[0]), pum2);}
24.qsort函数的模拟实现
相关文章:

指针万字超级最强i解析与总结!!!!!
文章目录 1.内存和地址1.1内存1.2究竟该如何理解编址 2.指针变量和地址2.1 取地址操作符(&)2.2指针变量和解引用操作符(*)2.2.1指针变量2.2.2如何拆解指针类型2.2.3解引用操作符 2.3 指针变量的大小 3.指针变量类型的意义3.1指…...

告别生硬电子音,这款TTS软件让语音转换更自然动听
Balabolka是一款革新性的文本语音转换工具,为用户提供了极其灵活和个性化的阅读体验。这款软件不仅仅是简单的文字朗读器,更是一个智能的语音助手,能够将各类文本瞬间转化为生动自然的语音输出。 软件的核心优势在于其卓越的文件兼容性和多样…...

CORS(跨域资源共享)和SOP(同源策略)
CORS(跨域资源共享)和SOP(同源策略)不是同一个东西,但它们紧密相关,并且常常一起讨论,因为 CORS 是为了解决同源策略带来的跨域问题而引入的。 同源策略(Same-Origin Policy&#x…...

【系统设计】数据库压缩技术详解:从基础到实践(附Redis内存优化实战案例)
概述 在现代数据库系统中,压缩技术对于提高存储效率和加速查询性能至关重要。特别是在处理大规模数据时,压缩能够极大地减少存储空间,并优化查询性能。本文将总结几种常见的压缩方式,并通过详细的解释和示例清晰地展示每种压缩方…...

基于SpringBoot的“乐校园二手书交易管理系统”的设计与实现(源码+数据库+文档+PPT)
基于SpringBoot的“乐校园二手书交易管理系统”的设计与实现(源码数据库文档PPT) 开发语言:Java 数据库:MySQL 技术:SpringBoot 工具:IDEA/Ecilpse、Navicat、Maven 系统展示 系统首页界面图 用户注册界面图 二手…...

debian11安装最新rabbitmq
1、使用官网提供系统对应的安装脚本 安装 版本说明: Debian Buster代表Debian 10 Debian Bullseye代表Debian 11 Debian Bookworm代表Debian 12 Debian Trixie代表Debian 13 Debian Sid代表Debian unstable版本 2、新建脚本文件 vim rabbitMq.sh将脚本内容复制到…...

三十三、Python基础语法(面向对象其他语法-下)
一、属性划分 1.类属性 类属性:类属性就是类对象具有的属性,一般写法在类内部、方法的外部定义的变量,就是类属性,类属性在内存中只有一份。可以通过类名直接访问,也可通过实例访问。 class Circle:# 类属性,定义圆…...

简单又便宜的实现电脑远程开机唤醒方法
现有的远程开机方案 1)使用向日葵开机棒 缺点是比较贵一点,开机棒要一百多,而且查了评论发现挺多差评说不稳定,会有断联和无法唤醒的情况,而且设置也麻烦,还需要网卡支持WOL 2)使用远程开机卡 …...

Flutter鸿蒙next 状态管理框架对比分析
在 Flutter 开发中,状态管理是一个非常重要且关键的主题。Flutter 中的应用状态管理直接影响着应用的性能、可维护性和开发效率。随着 Flutter 生态的成熟,已经出现了许多不同的状态管理方案,各具特色,适用于不同的开发场景。本文…...

Vue Router进阶详解
导航守卫 若依框架登录鉴权详解(动态路由)_若依鉴权-CSDN博客 完整的导航解析流程 导航被触发: 当用户点击页面中的链接、使用编程式导航(如router.push或router.replace)或手动输入URL时,导航流程被触发。…...

进程的控制
进程 task_struct mm_struct(虚拟地址空间) 页表 代码和数据 。 新建进程先有管理系统,然后才有代码和数据。 fork()函数:子进程返回0,父进程返回的是子进程的pid - - - 方便父进程对子进程标识。 进程终止:释放代码和数据占…...

基于C语言实现的图书管理系统
使用Visual Studio 2022编译工具进行编写代码的。 项目源码直接奉上: book1.h头文件: #ifndef __BOOK1_H //预处理用于条件编译 避免头文件反复包含 #define __BOOK1_H#include<stdio.h> #include <string.h> #include<stdlib.h> #include<stdbool.h&g…...

删除 需要来自XXXX的权限才能对此文件夹进行更改 文件的解决办法
如果你也是: 如果你也有类似上面的问题,这篇文章算是你看对了,哦哟! 我的牙齿现在是怨灵的牙齿,可以啃下一头牛。 翻遍千山万水,咱们也是终于取到真经了家人们。 首先下一个everything好吗 甩一个官网链…...

ARM base instruction -- ccmp (immediate)
Conditional Compare (immediate) sets the value of the condition flags to the result of the comparison of a register value and an immediate value if the condition is TRUE, and an immediate value otherwise. 此指令一般出现在 cmp 指令之后,表示双重比…...

高德 阿里231滑块 分析
声明: 本文章中所有内容仅供学习交流使用,不用于其他任何目的,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关! 有相关问题请第一时间头像私信联系我删…...

Unity 的 WebGL 构建中资源图片访问方式
在 Unity 的 WebGL 构建中,资源图片是可以打包在 工程内部 使用的,前提是这些资源被正确地包含在构建中,并且能够通过合适的方式加载和访问。不同于传统的本地文件访问,WebGL 需要通过 Asset Bundles、Addressables 或 Resources …...

WinForms 中使用 MVVM 模式构建应用:实现登录页面、页面导航及 SQLite 数据库连接完整框架搭建过程
前言 在传统的 WinForms 应用程序开发中,很多开发者使用事件驱动的设计模式,直接将业务逻辑编写在界面代码中。然而,随着应用程序的复杂性增加,单一的界面文件变得臃肿,难以测试和维护。借鉴 WPF 中 MVVM(…...

Chrome调试工具(查看CSS属性)
来说说这个Chrome调试工具吧,梦回gdb,但是它没有gdb难 打开浏览器 有两种方式可以直接打开Chrome调试工具 直接按F12 鼠标右键页面 --- 检查元素 什么mc玩家是鸣潮 标签页含义 🤒 elements查看标签结构(展示html文件&#…...

MQTT从入门到精通之MQTT入门
MQTT入门 1 MQTT概述 1.1 MQTT简介 MQTT(Message Queuing Telemetry Transport)由IBM于1999年开发的一种基于**"发布订阅模式"的轻量级的消息传输协议**! 发布订阅模式是一种传统的客户端-服务器架构的替代方案,因为…...

Hadoop生态系统主要包括哪些组件以及它们的作用
Hadoop生态系统是一个开源的大数据处理框架,它主要由一系列组件构成,每个组件都承担着不同的功能和作用。以下是Hadoop生态系统的主要组件及其作用的详细解释: HDFS(Hadoop Distributed File System) 作用:…...

OpenResty 1.27.1.1 已经正式发布
OpenResty 1.27.1.1 已经正式发布,这是一个基于 NGINX 和 LuaJIT 的 web 平台。以下是关于此次发布的一些重点信息和更新内容: 下载与安装 你可以在此处下载最新版本的 OpenResty。提供了便携式源代码分发、Win32/Win64 二进制分发以及为 Ubuntu、Debi…...

定高虚拟列表:让大数据渲染变得轻松
定高虚拟列表 基本认识 在数据如潮水般涌来的今天,如何高效地展示和管理这些数据成为了开发者们面临的一大挑战,传统的列表渲染方式在处理大量数据时,往往会导致页面卡顿、滚动不流畅等问题,严重影响用户体验(在页面…...

python request与grequests该如何选择
requests & grequests requests 和 grequests 是Python中用于发送HTTP请求的不同库。requests 是一个同步、阻塞式库,而 grequests 是基于 requests 封装的异步非阻塞库,它利用了 gevent 库提供的协程机制,能够并发发送多个请求。 选择…...

Unity3D UI 拖拽
Unity3D 实现 UI 元素拖拽功能。 UI 拖拽 通常画布上的 UI 元素都是固定位置的,我们可以通过实现拖拽接口,让 UI 元素可以被拖拽到其他位置。 拖拽接口 创建一个脚本 UIDrag.cs,在默认继承的 MonoBehaviour 后面,再继承三个接…...

介绍一下memcpy(c基础)
memcpy函数void *memcpy(void *dest, const void *src, size_t n); dest:指向目标内存区域的指针,即复制的目的地。src:指向源内存区域的指针,即要被复制的内容的来源。n:要复制的字节数 主要功能是将src所指向的内存…...

【网络面试篇】HTTP(2)(笔记)——http、https、http1.1、http2.0
目录 一、相关面试题 1. HTTP 与 HTTPS 有哪些区别? 2. HTTPS 的工作原理?(https 是怎么建立连接的) (1)ClientHello (2)SeverHello (3)客户端回应 &a…...

python-23-一篇文章帮你理解Python推导式
python-23-一篇文章帮你理解Python推导式 一.简介 在 Python 中,推导式(Comprehensions)是一个简洁的语法,用于通过某种可迭代对象快速生成新的对象(如列表、字典、集合等!来开始我们今天的日拱一卒&…...

WPF中如何简单的使用CommunityToolkit.Mvvm创建一个项目并进行 增删改查
目录 开始前准备的数据库dbblog如下: 第一步:创建项目后下载四个NuGet程序包 第二步:删除原本的MainWindow.XAML文件 并创建如下的目录结构 然后在View文件夹下面创建Login.XAML和Main.XAML 并且在App.XAML中将启动项改为Login.X…...

CesiumJS 案例 P15:检测标记、鼠标点击移动标记、鼠标拖动标记
CesiumJS CesiumJS API:https://cesium.com/learn/cesiumjs/ref-doc/index.html CesiumJS 是一个开源的 JavaScript 库,它用于在网页中创建和控制 3D 地球仪(地图) 一、检测标记 <!DOCTYPE html> <html lang"en&…...

Webserver(4.9)本地套接字的通信
目录 本地套接字 本地套接字 TCP\UDP实现不同主机、网络通信 本地套接字实现本地的进程间的通信,类似的,一般采用TCP的通信流程 生成套接字文件 #include<arpa/inet.h> #include<stdio.h> #include<stdlib.h> #include<unistd.h&…...