C语言-- 深入理解指针(4)
C语言-- 深入理解指针(4)
- 一、回调函数
- 二、冒泡排序
- 三、qsort函数
- 3.1 使用qsort函数排序整型数据
- 3.2 使用qsort函数排序double数据
- 3.3 使用qsort来排序结构体数据
- 四、模仿qsort库函数实现通用的冒泡排序
- 4.1 通用冒泡排序函数排序整型数据
- 4.2 通用冒泡排序函数排序结构体数据
书接上回:C语言-- 深入理解指针(3)
前言:在上一篇C语言–深入理解指针(3)中的末尾,我们讲到了转移表的实现有两种方法:使用函数指针数组 和 使用回调函数。第一种方法我们已经学习过了,下面我们就来尝试第二种方法——回调函数 。
一、回调函数
首先我们要知道回调函数是什么?
回调函数就是通过函数指针调用的函数。
如果你把一个函数的指针(地址)作为函数参数传给了另一个函数,当在另一个函数体内通过该函数指针来调用所指向的函数时,这个被调用的函数就是回调函数。
使用回调函数来实现计算器:
思考:
- 既然
case 1
到case 4
中的语句只有函数不一样,那么我们可以将其封装成一个函数Cal来简化代码冗余;- 函数名就相当于函数的地址(指针),四个运算函数的函数指针类型一样都是
int(*p)(int, int)
,所以需要进行什么操作(加减乘除),就可以将该操作的函数名(Add,Sub,Mul,Div),作为函数指针的参数传给Cal函数,在函数内部进行调用p(x, y)
,被调用的函数就是回调函数
#include <stdio.h>void menu()
{printf("****************************\n");printf("****** 计算器 ******\n");printf("****** 1.Add 2. Sub ******\n");printf("****** 3.Mul 4. 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 Cal(int(*p)(int, int))
{printf("请输入两个操作数:\n");int x = 0;int y = 0;int ret = 0;scanf("%d %d", &x, &y);ret = p(x, y);printf("%d\n", ret);
}int main()
{int input = 0;do{menu();printf("请选择您的操作:\n");scanf("%d", &input);switch (input){case 1:Cal(Add);break;case 2:Cal(Sub);break;case 3:Cal(Mul);break;case 4:Cal(Div);break;case 0:printf("计算结束,退出计算器...\n");break;default:printf("选择错误,请重新选择:\n");break;}} while (input);return 0;
}
二、冒泡排序
在编程中,我们有时可能会遇到一些需要将数组中的一组数据排序的问题,冒泡排序就是一种对整型数据进行排序的方法。
冒泡排序的核心思想是:两两相邻元素进行比较。
具体步骤:
- (以升序为例)每一次将两两相邻元素进行比较,如果前面一个元素大,就将两个元素交换位置,然后进行下一对比较,直到一趟比完,该趟所比较的数据中最大的数据就会被排到最后。
- 上一趟比较已经将一个最大的数放在了最后,所以下一趟比较就不需要再比较这个数,所以对剩余的数据进行下一趟排序
- 当只剩下最后一个数时,排序就完成了。 也就是说如果有n个数,那么就需要n - 1次趟排序,因为每一次交换是两个数据进行比较交换,n - 1趟排序已经分别将每一趟的最大数依次从后往前放在了最后面,即已经将n -
1个数据排好了,剩下的一个数据自然也就在最前面。
比如:给你一组数据:5 8 6 3 1 2 9 7
让你将其排为升序,即:1 2 3 5 6 7 8 9
第一趟排序:两两比较,将最大的数9排在了最后
第二趟排序:9已经排好了位置,所以不需要再比较和改变,于是对剩余的7个数进行排序:
以此类推,当第七趟排序后,前七位大的数已经从后往前分别排好了位置,那么最后一个最小的数1自然就在第一个位置。
代码如下:
void Bubble_sort(int arr[], int sz)
{int i = 0;for (i = 0; i < sz - 1; i++)//n个数,需要n - 1趟排序{int j = 0;for (j = 0; j < sz - 1 - i; j++)//前一趟已经排好了一个最大的数{ //所以每一趟需要排序的数据的个数都会少一个数//如果前一个数大于后一个数,就交换if (arr[j] > arr[j + 1]){int temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}}
}void print(int arr[], int sz)//打印
{int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}
}int main()
{int arr[] = { 5, 8, 6, 3, 1, 2, 9, 7 };int sz = sizeof(arr) / sizeof(arr[0]);Bubble_sort(arr, sz);print(arr, sz);return 0;
}
优化后的冒泡排序:
当我们需要排序的一组数据是一组接近有序的数据比如1 2 3 4 5 6 7 9 8时,此时仅仅需要一趟排序将8和9交换,即可完成最终排序。但是程序却还是需要每一趟一次次的排序,未免效率有些低下。
所以我们可以优化一下:
- 在每一趟排序之前,设置一个检测变量flag,令其为1,
int flag = 1;
;- 进入每一趟的循环,如果在该趟排序中发生了交换,将检测变量flag置为0:
flag = 0
,如果没有交换,什么也不需要做,则flag == 1
没有被改变;- 在趟数的循环内,在每一趟的循环外,当每一趟循环结束后,对检测变量flag进行检测: 如果
flag == 0
,说明该趟排序发生了交换,那就接着进行下一次排序; 如果flag == 1,
说明该趟排序没有发生交换,那就说明所有数据已经排序完成了,就直接使用break;退出循环,循环结束。
这样就可以提升冒泡排序的效率。
代码如下:
void Bubble_sort(int arr[], int sz)
{int i = 0;for (i = 0; i < sz - 1; i++)//n个数,需要n - 1趟排序{int flag = 1;int j = 0;for (j = 0; j < sz - 1 - i; j++)//前一趟已经排好了一个最大的数{ //所以每一趟需要排序的数据的个数都会少一个数//如果前一个数大于后一个数,就交换if (arr[j] > arr[j + 1]){int temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;flag = 0;}}if (flag == 1){break;}}
}
三、qsort函数
除了冒泡排序,在C语言中提供了一个快速排序的库函数qsort可以直接使用来帮助我们排序数据,而且它可以帮助我们排序各种类型的数据。
具体可以参照https://legacy.cplusplus.com/reference/cstdlib/qsort/?kw=qsort中所给的定义。
函数原型:
void qsort(void* base, size_t num, size_t size, int (*compar)(const void*, const void*));
使用qsort函数需要包含头文件:#include <stdlib.h>
参数:
base
表示指向需要排序的数组的第一个元素的指针,类型是void* 表示可以接受任意类型的元素的地址(因为qsort可以排序各种类型的数据);num
表示数组中的元素个数,类型是size_t
,无符号整型;size
表示数组中每个元素的大小,以字节为单位,类型是size_t
,无符号整型;int (*compar)(const void*, const void*)
表示指向比较两个元素的函数的指针(可以是函数名),该比较函数用来实现两个数据如何进行比较。(因为qsort可以排序各种类型的数据)
qsort库函数规定了:
比较函数的函数原型必须遵循以下形式:int compar (const void* p1, const void* p2);
即返回类型必须是int
,两个比较元素的参数必须是const void *
,变量名可以随便取
该函数只需要通过返回值的形式来决定元素的顺序:
如果p1指向的元素 > p2指向的元素,返回一个大于0的数;
如果p1指向的元素 = p2指向的元素,返回0;
如果p1指向的元素 < p2指向的元素,返回一个小于0的数;
比如如果对存放int类型的数组排成升序,比较函数就可以直接写成:
int cmp_int(const void* p1, const void* p2)//比较函数
{return *(int*)p1 - *(int*)p2;
}
解释:
-
首先p1和p2是
const void*
类型的指针变量,所以不能直接解引用得到数据; -
数组元素是int类型,所以要先将其强制类型转换为
(int*)
类型,然后再解引用拿到p1和p2指向的两个数; -
如果p1比p2指向的数大,根据qsort对比较函数的定义,要返回一个大于0的数,那么既然p1比p2指向的数大,他俩的差值必然>0。
同理如果p1比p2指向的数小于或者相等,二者差值一样会 <0 或者 = 0; -
所以直接返回二者差值就能达到目的;
当然如果对数组降序排序,将两个指针换个位置就行:
int cmp_int(const void* p1, const void* p2)//比较函数
{return *(int*)p2 - *(int*)p1;
}
3.1 使用qsort函数排序整型数据
#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]);}
}int cmp_int(const void* p1, const void* p2)//比较函数
{return *(int*)p1 - *(int*)p2;
}int main()
{int arr[] = { 1,3,5,7,9,2,4,6,8,10 };size_t sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(int), cmp_int);print(arr, sz);return 0;
}
3.2 使用qsort函数排序double数据
使用qsort库函数对于存放double类型数据的数组排序,比较函数就不能直接返回两个元素相减的值了,因为两个double得出来的还是一个double的数,而函数定义中规定了比较函数的返回值必须是int类型的数。
这时肯定会有人那将这两个double类型的数的差值强制类型转换成int类型不就行了吗?这种写法肯定是错误的,因为从double到int的强制类型转换只会取整数部分,如果两个double类型的数的差值的绝对值小于1,那么强制类型转换的结果就是0,就会造成错误。
所以可以使用if语句或者直接使用三目操作符来解决:
#include <stdio.h>
#include <stdlib.h>void print_double(double arr[], int sz)
{int i = 0;for (i = 0; i < sz; i++){printf("%lf ", arr[i]);}
}int cmp_double(const void* p1, const void* p2)
{return *(double*)p1 > *(double*)p2 ? 1 : -1;//return *(double*)p1 < *(double*)p2 ? 1 : -1;//降序
}int main()
{double arr[] = { 1.0,3.0,5.0,7.0,9.0,2.0,4.0,6.0,8.0 };size_t sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(double), cmp_double);print_double(arr, sz);return 0;
}
3.3 使用qsort来排序结构体数据
比如我们需要对学生的信息排序,一个学生的信息包括年龄和姓名,我们将其包装成结构体:
struct Student
{char name[20];int age;
};
- 对该结构体排序,如果对name字符串排序
学生姓名是字符串,放到name字符数组中,所以我们需要写对于结构体中的字符串的比较函数,我们使用strcmp字符比较函数,刚好可以满足qsort库函数对于比较函数的返回值的要求
当然使用strcmp库函数需要包含头文件#include <string.h>
int strcmp ( const char * str1, const char * str2 );
如果str1指向的字符串比str指向的字符串大,那么返回一个>0的数字;
如果str1指向的字符串比str指向的字符串一样,那么返回一个=0的数字;
如果str1指向的字符串比str指向的字符串小,那么返回一个<0的数字;
具体函数介绍可以参考:https://legacy.cplusplus.com/reference/cstring/strcmp/?kw=strcmp
于是可以写出比较函数:
注意结构体指针对结构体成员的访问操作符->
的优先级比强制类型转换的括号( )高,所以在对const void*
类型的p1和p2强制类型转换为结构体指针(struct Student*)p1
后再加上括号才能使用->
对结构体成员进行访问。
int cmp_by_name(const void* p1, const void* p2)
{return strcmp(((struct Student*)p1)->name, ((struct Student*)p2)->name);
}
完整代码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>struct Student
{char name[20];int age;
};int cmp_by_name(const void* p1, const void* p2)
{return strcmp(((struct Student*)p1)->name, ((struct Student*)p2)->name);
}int main()
{struct Student s[] = { {"zhangsan",37},{"lisi",22},{"wangwu", 19} };int sz = sizeof(s) / sizeof(s[0]);qsort(s, sz, sizeof(s[0]), cmp_by_name);return 0;
}
- 同理,如果是对结构体中的age整型排序
对于比较函数来说,首先要将比较函数的参数类型const void*
的指针变量,强制类型转换为指向结构体的指针struct Student*
,加上括号然后通过->访问结构体成员age;
此时就是对于一个int类型的排序,就直接二者相减就行:
int cmp_by_age(const void* p1, const void* p2)
{return ((struct Student*)p1)->age - ((struct Student*)p2)->age;
}
完整代码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>struct Student
{char name[20];int age;
};int cmp_by_age(const void* p1, const void* p2)
{return ((struct Student*)p1)->age - ((struct Student*)p2)->age;
}int main()
{struct Student s[] = { {"zhangsan",37},{"lisi",22},{"wangwu", 19} };int sz = sizeof(s) / sizeof(s[0]);qsort(s, sz, sizeof(s[0]), cmp_by_age);return 0;
}
四、模仿qsort库函数实现通用的冒泡排序
一般来说冒泡排序是用来排序整型数据的,我们了解了qsort库函数的实现逻辑,那么我们也可以尝试模仿qsort库函数来模拟实现一个可以排序任意类型数据的通用的冒泡排序。
普通冒泡排序:
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 temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}}
}
对于通用的冒泡排序,基于普通冒泡排序的改造分析:
- 首先如果想要使用冒泡排序来排序任意类型的数据,参数的类型和数量毫无疑问是要改变的,
void bubble_sort(void* base, size_t num, size_t size, int (*compar)(const void*, const void*));
base
表示指向需要排序的数组的第一个元素的指针,类型是void* 表示可以接受任意类型的元素的地址num
表示数组中的元素个数,类型是size_t
,无符号整型;size
表示数组中每个元素的大小,以字节为单位,类型是size_t
,无符号整型;int (*compar)(const void*, const void*)
表示指向比较两个元素的函数的指针(可以是函数名),该比较函数用来实现两个元素如何进行比较。
- 其次,对于冒泡排序的趟数和每一趟的比较逻辑的两层
for
循环框架不需要改变(要不然就不是冒泡了哈哈); - 因为可以排序任意类型的数据,所以对于
if
语句中对于两个元素大小的比较判断,需要改为一个比较函数compar
,需要比较什么类型的数据,就传参比较这种类型数据的函数。这也是为什么函数参数中需要一个函数指针 - 比完了大小,如果需要交换,同理那么对于任意类型的数据的交换肯定也需要改为一个交换函数
Swap
,来交换任意类型的数据。
void bubble_sort(void* base, size_t num, size_t size, int (*compar)(const void*, const void*))
{
//函数里面实则还是冒泡排序int i = 0;for (i = 0; i < num - 1; i++){int j = 0;for (j = 0; j < num - i - 1; j++){//进行判断和交换}}
}
通过分析,我们需要编写比较判断函数compar
和交换函数Swap
:
对于比较函数,和qsort库函数的比较函数是一样的,直接在if语句中判断是否>0;
如果>0,则交换;否则不交换。
但是函数需要传两个参数,分别是指向两个元素的指针(地址),所以就需要使用到其他的参数来表示这两个元素的地址了。
base
是数组的首元素的地址,size
是数组每一个元素的大小,j
代表数组元素的下标,所以(char*)base + j * size
,就可以表示第j
个数组元素的指针(地址)-- (base在函数声明中是void*
类型,需要强制类型转换为(char*
),所以 + j * size
就表示跳过 j * size个字节,从而到达可以遍历每一对元素的目的), 而(char*)base + (j + 1) * size
就可以表示第j + 1
个数组元素的指针(地址)
if (compar((char*)base + j * size, (char*)base + (j + 1) * size) > 0)//进行判断
{//进行交换
}
对于交换函数,既然要达到可以交换任意类型的目的,肯定不能像之前那样直接创建一个int类型的临时变量,每一次交换4个字节对二者进行交换了。
因为类型不可知,有可能是普通类型,也有可能是自定义类型,所以不能采用直接创建临时变量一次性交换的方法。
但是类型大小最小起码是一个字节,所以我们可以一个字节一个字节的交换,因为通用冒泡排序函数中有参数size来代表类型的大小,可以作为交换循环的结束条件。
void Swap(char* ptr1, char* ptr2, size_t size)
{int i = 0;for (i = 0; i < size; i++){char temp = *ptr1;*ptr1 = *ptr2;*ptr2 = temp;ptr1++;ptr2++;}
}
ptr1就传参(char*)base + j * size
,ptr2就传参(char*)base + (j + 1) * size
完整代码:
注:比较函数compar有具体传参决定。
void Swap(char* ptr1, char* ptr2, size_t size)
{int i = 0;for (i = 0; i < size; i++){char temp = *ptr1;*ptr1 = *ptr2;*ptr2 = temp;ptr1++;ptr2++;}
}void bubble_sort(void* base, size_t num, size_t size, int (*compar)(const void*, const void*))
{int i = 0;for (i = 0; i < num - 1; i++){int j = 0;for (j = 0; j < num - i - 1; j++){if (compar((char*)base + j * size, (char*)base + (j + 1) * size) > 0){Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);}}}
}
4.1 通用冒泡排序函数排序整型数据
void Swap(char* ptr1, char* ptr2, size_t size)
{int i = 0;for (i = 0; i < size; i++){char temp = *ptr1;*ptr1 = *ptr2;*ptr2 = temp;ptr1++;ptr2++;}
}void bubble_sort(void* base, size_t num, size_t size, int (*compar)(const void*, const void*))
{int i = 0;for (i = 0; i < num - 1; i++){int j = 0;for (j = 0; j < num - i - 1; j++){if (compar((char*)base + j * size, (char*)base + (j + 1) * size) > 0){Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);}}}
}int cmp_int(const void* p1, const void* p2)
{return *(int*)p1 - *(int*)p2;
}print_int(int arr[], int sz)//输出打印
{int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}
}int main()
{int arr[] = { 9,8,7,6,5,4,3,2,1 };int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);print_int(arr, sz);return 0;
}
4.2 通用冒泡排序函数排序结构体数据
struct Student
{char name[20];int age;
};int cmp_by_name(const void* p1, const void* p2)
{return strcmp(((struct Student*)p1)->name, ((struct Student*)p2)->name);
}int cmp_by_age(const void* p1, const void* p2)
{return ((struct Student*)p1)->age - ((struct Student*)p2)->age;
}void Swap(char* ptr1, char* ptr2, size_t size)
{int i = 0;for (i = 0; i < size; i++){char temp = *ptr1;*ptr1 = *ptr2;*ptr2 = temp;ptr1++;ptr2++;}
}void bubble_sort(void* base, size_t num, size_t size, int (*compar)(const void*, const void*))
{int i = 0;for (i = 0; i < num - 1; i++){int j = 0;for (j = 0; j < num - i - 1; j++){if (compar((char*)base + j * size, (char*)base + (j + 1) * size) > 0){Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);}}}
}int main()
{struct Student s[] = { {"zhangsan",37},{"lisi",22},{"wangwu", 19} };int sz = sizeof(s) / sizeof(s[0]);bubble_sort(s, sz, sizeof(s[0]), cmp_by_name);//比较结构体中的name//bubble_sort(s, sz, sizeof(s[0]), cmp_by_age);//比较结构体中的agereturn 0;
}
结语:C语言-- 深入理解指针(4) 章节到这里就结束了。
本人才疏学浅,文章中有错误和有待改进的地方欢迎大家批评和指正,非常感谢您的阅读!如果本文对您又帮助,可以高抬贵手点点赞和关注哦!
相关文章:
C语言-- 深入理解指针(4)
C语言-- 深入理解指针(4) 一、回调函数二、冒泡排序三、qsort函数3.1 使用qsort函数排序整型数据3.2 使用qsort函数排序double数据3.3 使用qsort来排序结构体数据 四、模仿qsort库函数实现通用的冒泡排序4.1 通用冒泡排序函数排序整型数据4.2 通用冒泡排…...
牟乃夏《ArcGIS Engine地理信息系统开发教程》学习笔记3-地图基本操作与实战案例
目录 一、开发环境与框架搭建 二、地图数据加载与文档管理 1. 加载地图文档(MXD) 2. 动态添加数据源 三、地图浏览与交互操作 1. 基础导航功能 2. 书签管理 3. 量测功能 四、要素选择与属性查询 1. 属性查询 2. 空间查询 五、视图同步与鹰眼…...
Spark Streaming实时数据处理实战:从DStream基础到自定义数据源集成
park-Streaming概述 Spark-Streaming是什么 Spark Streaming 用于流式数据的处理。Spark Streaming 支持的数据输入源很多,例如:Kafka、Flume、Twitter等,以及和简单的 TCP 套接字等等。数据输入后可以用 Spark 的高度抽象原语如:…...
微软GraphRAG的安装和在RAG中的使用体会
文章目录 0. 简介(1)**技术原理**(2)**优势**(3)**开源与演进** 1. 下载graphrag.git2.安装 poetry3.初始化项目:建立cases目录4. 修改.env5.修改settings.yaml,将两处 api_base改成中转站地址:…...
Python学习记录7——集合set()的使用指南
文章目录 引言一、集合特性二、创建方式三、元素操作1、添加元素(1)add(element)(2)update(iterables) 2、删除元素(1)remove(element)(2)discard(element)(3)…...
apkpure 谷歌插件 下载的apk包
谷歌插件市场搜索 apkpure 然后直接搜索下载就行了 想看apk包中的静态资源,直接改apk 为zip后缀解压就行了 apple的ipa包也是相同的道理...
Android四大核心组件
目录 一、为什么需要四大组件? 二、Activity:看得见的界面 核心功能 生命周期图解 代码示例 三、Service:看不见的劳动者 两大类型 生命周期对比 注意陷阱 四、BroadcastReceiver:消息传递专员 两种注册方式 广播类型 …...
WSL2里手动安装Docker 遇坑
在 WSL2 里手动安装 Docker Engine 时遇坑:systemctl 和 service 命令在默认的 WSL2 Ubuntu 中 无法使用,因为 WSL2 没有 systemd。怎么办? 自己操作让 Docker Engine(dockerd)直接跑起来,挂到 /var/run/do…...
【ROS2】ROS开发环境配置——vscode和git
古月21讲-ROS2/1.系统架构/1.5_ROS2开发环境配置/ ROS机器人开发肯定离不开代码编写,课程中会给大家提供大量示例源码,这些代码如何查看、编写、编译 安Linux中安装装git sudo apt install git下载教程源码 《ROS2入门21讲》课程源码的下载方式&#x…...
django.db.models.query_utils.DeferredAttribute object
在 Django 中,当你看到 django.db.models.query_utils.DeferredAttribute 对象时,通常是因为你在查询时使用了 only() 或 defer() 方法来延迟加载某些字段。这两个方法允许你控制数据库查询中的字段加载方式,从而优化查询性能。 only() 方法…...
Linux内核中的编译时安全防护:以网络协议栈控制块校验为例
引言:内存安全的无声守卫者 在操作系统内核开发中,内存溢出引发的错误往往具有极高的隐蔽性和破坏性。Linux内核作为承载全球数十亿设备的基石,其网络协议栈的设计尤其注重内存安全性。本文通过分析一段看似简单的内核代码,揭示Linux如何通过编译时静态检查(Compile-Time…...
第11章 安全网络架构和组件(一)
11.1 OSI 模型 协议可通过网络在计算机之间进行通信。 协议是一组规则和限制,用于定义数据如何通过网络介质(如双绞线、无线传输等)进行传输。 国际标准化组织(ISO)在20世纪70年代晚期开发了开放系统互连(OSI)参考模型。 11.1.1 OSI模型的…...
Git常用命令简明教程
本教程整合并优化了Git核心命令,涵盖初始化、配置、文件操作、分支管理、远程仓库操作及常见场景,适合快速入门和日常参考。命令按使用流程分组,简洁明了,包含注意事项和最佳实践。 1. 初始化与配置 初始化Git仓库并设置基本配置…...
在 Ubuntu 24.04 系统上安装和管理 Nginx
1、安装Nginx 在Ubuntu 24.04系统上安装Nginx,可以按照下面的步骤进行: 1.1、 更新系统软件包列表 在安装新软件之前,需要先更新系统的软件包列表,确保获取到最新的软件包信息。打开终端,执行以下命令: …...
数据结构——二叉树和堆(万字,最详细)
目录 1.树 1.1 树的概念与结构 1.2 树相关的术语 1.3 树的表示法 2.二叉树 2.1 概念与结构 2.2 特殊的二叉树 2.2.1 满二叉树 2.2.2 完全二叉树 2.3 二叉树存储结构 2.3.1 顺序结构 2.3.2 实现顺序结构二叉树 2.3.2.1 堆的概念与结构 2.3.2. 2 堆的插入与删除数据…...
IdeaVim 配置与使用指南
一、什么是 IdeaVim? IdeaVim 是 JetBrains 系列 IDE(如 IntelliJ IDEA, WebStorm, PyCharm 等)中的一个插件,让你在 IDE 里使用 Vim 的按键习惯,大大提升效率。 安装方法: 在 IDE 中打开 设置(Settings) →…...
前端浏览器窗口交互完全指南:从基础操作到高级控制
浏览器窗口交互是前端开发中构建复杂Web应用的核心能力,本文深入探讨23种关键交互技术,涵盖从传统API到最新的W3C提案,助您掌握跨窗口、跨标签页的完整控制方案。 一、基础窗口操作体系 1.1 窗口创建与控制 // 新窗口创建(现代浏…...
考研系列-计算机组成原理第五章、中央处理器
一、CPU的功能及结构 1.运算器的基本结构 2.控制器结构...
python+flask+flask-sockerio,部署后sockerio通信异常
前言 用python开发了一个flask web服务,前端用html,前后端通过socketio通信,开发环境,windowsminicondavscode,开发完成后本地运行没有问题,然后就开始部署,噩梦就开始了。 问题描述 程序是部…...
深度解析:TextRenderManager——Cocos Creator艺术字体渲染核心类
一、类概述 TextRenderManager 是 Cocos Creator 中实现动态艺术字体渲染的核心单例类。它通过整合资源加载、缓存管理、异步队列和自动布局等功能,支持普通字符模式和图集模式两种渲染方案,适用于游戏中的动态文本(如聊天内容、排行榜&…...
同样开源的自动化工作流工具n8n和Dify对比
n8n和Dify作为两大主流工具,分别专注于通用自动化和AI应用开发领域,选择哪个更“好用”需结合具体需求、团队能力及业务场景综合判断。以下是核心维度的对比分析: 一、核心定位与适用场景 维度n8nDify核心定位开源全场景自动化工具ÿ…...
设计模式每日硬核训练 Day 16:责任链模式(Chain of Responsibility Pattern)完整讲解与实战应用
🔄 回顾 Day 15:享元模式小结 在 Day 15 中,我们学习了享元模式(Flyweight Pattern): 通过共享对象,分离内部状态与外部状态,大量减少内存开销。适用于字符渲染、游戏场景、图标缓…...
基于边缘人工智能的AI无人机-更高效更安全的飞行任务执行
基于边缘人工智能的AI无人机-更高效更安全的飞行任务执行 人工智能有可能改变人们的生活和工作方式。人工智能和无人机是近年来发展迅速的两项技术。当这两种技术结合在一起时,它们会创造出许多以前不可能的应用。基于人工智能的无人机旨在独立执行任务,…...
30、不是说字符串是不可变的吗,string s=“abc“;s=“123“不就是变了吗?
一、核心概念澄清:不可变性的真实含义 1、不可变性的定义 字符串不可变性指对象内容不可修改,而非变量不可修改。 类比: 不可变字符串 装在密封信封里的信纸(内容不可更改)变量赋值 更换信封的指向(从…...
线上查询车辆出险记录:快速掌握事故情况!
在如今汽车成为人们日常不可或缺的交通工具之际,车辆出险记录成为了许多车主关注的焦点之一。为了帮助车主们快速了解车辆出险、理赔、事故记录,现在有了一种便捷的方式,那就是通过API接口在线查询。本文将介绍如何利用API接口,通…...
Python爬虫课程实验指导书
1.1Requests类库的认知 1.1.1 认识请求类库 Requests是用Python语言编写,基于,采用Apache2 Licensed开源协议的。它比urllib更加方便,可以节约我们大量的工作,完全满足HTTP测试需求。urllibHTTP库 Requests官网地址:ht…...
streamlit实现非原生的按钮触发效果 + flask实现带信息的按钮触发
目录 简介不携带信息的触发隐藏指定st.button(label, key)触发button的html代码汇总 携带信息的触发为什么需要携带信息前端JavaScript修改flask处理总代码 简介 由于streamlit可以同时在实现前后端结合,非常方便,但是这也造成了user难以方便的对页面的…...
机器学习基础——Seaborn使用
1.使用tips数据集,创建一个展示不同时间段(午餐/晚餐)账单总额分布的箱线图 import matplotlib.pyplot as plt import numpy as np import pandas as pd import seaborn as snstips pd.read_csv(./tips.csv)sns.boxplot(data tips,x time,y total_bill, )plt.show() 2.使用…...
Godot开发2D冒险游戏——第三节:游戏地图绘制
一、初步构建游戏地图 在游戏场景当中添加一个新的子节点:TileMapLayer 这一层称为瓦片地图层 根据提示,下一步显然是添加资源 为TileMapLayer节点添加一个TileSet 将地板添加进来,然后选择自动分割图集 自定义时要确保大小合适 让Godot自…...
Spark Mllib 机器学习
概述 机器学习是什么 根据百度百科的定义: 机器学习是一种通过算法和模型使计算机从数据中自动学习并进行预测或决策的技术。 定义比较抽象,根据常见的机器学习可以总结出三个关键字: 算法、经验、性能。 机器学习的过程可以抽象成一个pipel…...
在windows使用docker打包springboot项目镜像并上传到阿里云
1、前提:已有spring项目 2、在项目根目录下创建Dockerfile文件 FROM openjdk:11 WORKDIR /ruoyi-admin COPY ruoyi-admin/build/libs/lifecolor-web.jar lifecolor-web.jar CMD ["java", "-jar", "lifecolor-web.jar"] 3、选…...
前端高频面试题day3
JavaScript作用域理解 核心概念 作用域:定义变量/函数的可见范围及生命周期,分为 全局作用域、函数作用域、块级作用域。作用域链:变量查找从当前作用域逐级向上直至全局,遵循词法作用域(静态作用域)。闭…...
时空特征如何融合?LSTM+Resnet有奇效,SOTA方案预测准确率超91%
LSTM有着不错的时序信息提取能力,ResNet有着不错的空间特征信息提取能力。如果现在有时空特征融合的创新需求,我们是否能将LSTM和ResNet两者的优点融合起来呢? 随着这个思路下去,LSTM ResNet混合模型横空出世,在各个…...
蓝桥杯Java全攻略:从零到一掌握竞赛与企业开发实战
蓝桥杯Java软件开发竞赛已成为全国高校学生展示编程能力的重要舞台,本指南将带您从零开始构建完整的Java知识体系,不仅覆盖蓝桥杯高频考点,还延伸至企业级开发实战,助您在竞赛中脱颖而出并为未来职业发展奠定坚实基础。 一、Java基础语法与数据结构 竞赛解题流程图设计 蓝…...
【Nginx】负载均衡配置详解
Nginx作为高性能的HTTP服务器和反向代理服务器,提供了强大的负载均衡功能。本文将详细介绍Nginx负载均衡的配置方法和相关策略。 一、基础负载均衡配置 1.单服务示例配置 配置nginx.conf模块 在Nginx配置文件中定义upstream模块: worker_processes a…...
打造企业级AI文案助手:GPT-J+Flask全栈开发实战
一、智能文案革命的序幕:为什么需要AI文案助手? 在数字化营销时代,内容生产效率成为企业核心竞争力。据统计,营销人员平均每天需要撰写3.2篇文案,而传统人工创作存在三大痛点: 效率瓶颈:创意构…...
【文献速递】snoRNA-SNORD113-3/ADAR2通过对PHKA2的A-to-I编辑影响胶质母细胞瘤糖脂代谢
Cui等人于2025年在Cellular & Molecular Biology Letters上的发表一篇研究论文,题目为“Effect of SNORD113-3/ADAR2 on glycolipid metabolism in glioblastoma via A-to-I editing of PHKA2”。这篇文章的核心内容是研究胶质母细胞瘤(GBMÿ…...
视频HLS分片与关键帧优化深度解析
视频HLS分片与关键帧优化深度解析 🌐 HLS基础架构 #mermaid-svg-OQmrXfradiCv3EGC {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-OQmrXfradiCv3EGC .error-icon{fill:#552222;}#mermaid-svg-OQmrXfrad…...
再谈从视频中学习:从给视频打字幕的Humanoid-X、UH-1到首个人形VLA Humanoid-VLA:迈向整合第一人称视角的通用人形控制
前言 本博客内,之前写了比较多的大脑相关的,或者上肢操作,而解读运动控制、规划的虽也有,但相对少 故近期 准备多写写双足人形的运动控制相关 一方面,我们有些客户订单涉及这块二方面,想让双足人形干好活…...
Ubuntu下MySQL的安装
Ubuntu下MySQL的安装 1. 查看当前操作系统版本2. 添加MySQL APT源2.1 访问下载页面,并下载发布包2.2 执行安装指令2.3 安装MySQL 3. 查看MySQL状态4. 设置开机自启动 1. 查看当前操作系统版本 通过命令lsb_release -a查看: 2. 添加MySQL APT源 2.1 访问下…...
DataStreamAPI实践原理——快速上手
引入 通过编程模型,我们知道Flink的编程模型提供了多层级的抽象,越上层的API,其描述性和可阅读性越强,越下层API,其灵活度高、表达力越强,多数时候上层API能做到的事情,下层API也能做到&#x…...
《数据结构初阶》【顺序表 + 单链表 + 双向链表】
《数据结构初阶》【顺序表 单链表 顺序表】 前言:先聊些其他的东西!!!什么是线性表?什么是顺序表?顺序表的种类有哪些? 什么是链表?链表的种类有哪些? ---------------…...
【JS-Leetcode】2621睡眠函数|2629复合函数|2665计数器||
文章目录 2621睡眠函数2629复合函数2665计数器|| 这三个题目涉及setTimeout、promise、数组reduce方法,闭包。 2621睡眠函数 请你编写一个异步函数,它接收一个正整数参数 millis ,并休眠 millis 毫秒。要求此函数可以解析任何值。 原理&am…...
全国各地级城市月度平均房价统计数据2009-2021年
全国各地级城市月度平均房价统计数据2009-2021年.ziphttps://download.csdn.net/download/2401_84585615/90259770 https://download.csdn.net/download/2401_84585615/90259770 来源:安居客,本数据以excel格式展示,列举2.5万多条样本数据。总…...
ElasticSearch从入门到精通-覆盖DSL操作和Java实战
一、ElasticSearch基础概念 1.1 认识elasticSearch ElasticSearch(简称ES)是一款开源的、分布式的搜索引擎,它建立在Apache Lucene之上。简单来说,ElasticSearch就是一个能让你以极快速度进行数据搜索、存储和分析的系统。它不仅…...
SHCTF-REVERSE
前言 之前写的,一直没发,留个记录吧,万一哪天记录掉了起码在csdn有个念想 1.ezapk 反编译 快速定位关键函数 package com.mycheck.ezjv;import adrt.ADRTLogCatReader; import android.app.Activity; import android.content.Context; impo…...
C++学习:六个月从基础到就业——模板编程:模板特化
C学习:六个月从基础到就业——模板编程:模板特化 本文是我C学习之旅系列的第三十四篇技术文章,也是第二阶段"C进阶特性"的第十二篇,主要介绍C中的模板特化技术。查看完整系列目录了解更多内容。 目录 引言模板特化基础…...
【中级软件设计师】编译和解释程序的翻译阶段、符号表 (附软考真题)
【中级软件设计师】编译和解释程序的翻译阶段、符号表 (附软考真题) 目录 【中级软件设计师】编译和解释程序的翻译阶段、符号表 (附软考真题)一、历年真题二、考点:编译和解释程序的翻译阶段1、解释2、编译3、解释和编译的异同之处4、符号表 三、真题的答案与解析答…...
G1(Garbage-First)垃圾回收器与JVM内存
G1垃圾回收器简介 G1(Garbage-First)是Java虚拟机(JVM)中的一种垃圾回收器,它是针对服务器端应用设计的,旨在提供高吞吐量和低延迟的垃圾回收性能。G1垃圾回收器的主要目标是高效地管理JVM的堆内存,同时尽量减少垃圾回收(GC)过程对应用程序性能的影响。 特点 分区回收…...
STM32 驱动 INA226 测量电流电压功率
文章目录 一、INA226简介二、引脚功能三、寄存器介绍1.配置寄存器 0x002.分流电压寄存器 0x013.总线电压寄存器 0x024.功率寄存器 0x035.电流寄存器 0x046.基准寄存器 0x05 四、IIC 时序说明1.写时序2.读时序 五、程序六、实验现象1.线路图2.输出数据 一、INA226简介 INA226 是…...