深入理解回调函数qsort:从入门到模拟实现
🍁博客主页:江池俊的博客
💫收录专栏:C语言进阶之路
💡代码仓库:江池俊的代码仓库
🎪我的社区:GeekHub
🎉欢迎大家点赞👍评论📝收藏⭐

文章目录
- 前言
- 一、什么是回调函数?
- 📌使用回调函数的优势
- 二、`qsort` 函数及其用法
- 📌qsort函数作用
- 📌qsort函数4个参数的介绍
- 📌为什么qsort函数的参数是这四个?
- 📌第4个参数--->compar比较函数的剖析
- 三、qsort函数实例
- 📌排序int类型数组
- 📌排序char类型数组
- 📌排序浮点型数组
- 📌排序结构体类型数组
- ``1. 【按姓名来排序】``
- `` 2. 【按年龄来排序】``
- 四、模拟实现qsort函数
- 🧩冒泡排序
- 🧩bubble_sort函数(模拟实现的qsort函数)
- 🚩Swap函数剖析
- 🧩利用bubble_sort函数排序整型数组
- 🧩利用bubble_sort函数排序结构体数组
- ``1. 【按姓名来排序】``
- ``2. 【按年龄来排序】``
- 总结
前言
回调函数和
qsort是 C语言编程中重要的概念,它们为我们提供了强大的工具,用于处理函数指针和数组排序。本篇博客将逐步介绍回调函数的概念,详细解释qsort函数的用法,并通过一个模拟实现,帮助初学者更好地理解这些概念。如果大家不知道函数指针是说明或还不清楚函数指针的内容,可以移步我这篇文章《掌握指针进阶:一篇带你玩转函数指针、函数指针数组及指向函数指针数组的指针!!》
一、什么是回调函数?
回调函数是一种通过函数指针传递给其他函数,并由其他函数在适当时候调用的函数。回调函数的存在使得我们能够将某种特定的行为(代码逻辑)作为参数传递给另一个函数。这在编程中非常有用,因为它允许我们以灵活的方式自定义函数的行为。
📌使用回调函数的优势
- 代码重用: 可以将通用的操作封装在回调函数中,以供多个函数重复使用。
- 灵活性: 回调函数允许我们在运行时动态地指定要执行的代码,从而实现更高度的灵活性。
- 解耦合: 使用回调函数可以将代码分解成独立的模块,减少模块之间的耦合,提高代码的可维护性。
二、qsort 函数及其用法
qsort 是 C 标准库中提供的用于数组排序的函数,它接受一个 比较函数 作为参数,用于确定数组元素的顺序。(这个比较函数是使用者根据自己的需要设计的,因此qsort函数可以实现对任意类型数据的排序)qsort 函数的原型如下:
void qsort (void* base, size_t num, size_t size,int (*compar)(const void*,const void*));
其中,base 是要排序的数组的指针;num 是数组中元素的数量;size 是每个元素的大小,以字节为单位;compar 是用于比较两个元素的函数指针。
这里我们可以通过cplusplus网来查询这个函数的使用方法,也可以使用菜鸟教程网来查询。
📌qsort函数作用

📌qsort函数4个参数的介绍

📌为什么qsort函数的参数是这四个?
qsort 函数之所以有这四个参数,是为了实现通用、灵活且可定制的排序功能。
这些参数的设计和使用有以下几个目的:
通用性: 由于
qsort需要适应不同类型的数据,它通过base参数接受数组的指针,并使用size参数来了解每个元素的大小,从而使得排序操作可以应用于各种不同类型的数组。灵活性: 通过传递比较函数的指针作为
compar参数,我们可以在不同的排序场景中定义不同的比较逻辑。这使得我们可以根据需要实现升序、降序或自定义的排序规则。可定制性:
qsort的设计允许我们在排序过程中自定义元素的比较方式。我们可以根据实际需求提供不同的比较函数,从而实现不同的排序需求。高效性:
qsort内部使用一种高效的排序算法(通常是快速排序的变种),以确保在大多数情况下能够高效地完成排序操作。
综上所述,这四个参数的设计使得 qsort 函数成为一个强大且通用的排序工具,可以适应不同类型的数据、实现不同的排序规则,并且在实际应用中能够高效地完成排序任务。
📌第4个参数—>compar比较函数的剖析

在 qsort 函数中,要实现升序或降序排序,这需要根据比较函数的逻辑来确定元素的顺序。比较函数的返回值将决定元素的排列方式。
- 如果比较函数返回负值,
qsort将认为第一个元素应该在第二个元素之前,从而实现升序排序。 - 如果比较函数返回正值,
qsort将认为第一个元素应该在第二个元素之后,从而实现降序排序。 - 如果比较函数返回零,
qsort将认为两个元素相等,它们的顺序将是未定义的。
注意:
- qsort 函数的比较函数参数是两个 void 类型的指针,是为了提高灵活性和通用性。这样的设计允许您在不同的排序场景中使用同一个 qsort 函数,无论排序的数据类型是什么。
- 当编写一个通用的排序函数时,我们无法预先知道要排序的数据类型是什么。因此,将比较函数的参数声明为 void 类型的指针,使得 qsort 函数可以接受任何类型的数据。
由此,我们可以得到qsort函数的使用模板如下:
#include <stdio.h>
#include <stdlib.h>// 定义一个的数据类型(示例:整数类型)
typedef int MyType;// 比较函数
int compareMyType(const void *a, const void *b) {return (*(MyType *)a - *(MyType *)b);//实现升序//return (*(MyType *)b - *(MyType *)a);//实现降序
}int main() {int numElements = ...; // 数组中元素的数量MyType arr[numElements]; // 声明并初始化一个数组// 使用 qsort 对数组进行排序qsort(arr, numElements, sizeof(MyType), compareMyType);// 打印排序后的数组for (int i = 0; i < numElements; i++) {printf("%d ", arr[i]); // 打印数组元素}return 0;
}
三、qsort函数实例
注意:以下统一以升序为例
📌排序int类型数组
代码展示:
#include<stdio.h>
#include<stdlib.h>
//实现一个比较整型的函数
int compare_int(const void* a, const void* b)
{return *(int*)a - *(int*)b;//强制转换为int类型并解引用
}//使用qsort对数组进行排序,升序
int main()
{int arr[] = { 9,8,7,6,5,4,3,2,1,0 };int sz = sizeof(arr) / sizeof(arr[0]);printf("排序前:");for (int i = 0; i < sz; i++){printf("%d ", arr[i]);}//排序qsort(arr,sz,sizeof(int),compare_int);//打印printf("\n排序后:");for (int i = 0; i < sz; i++){printf("%d ", arr[i]);}printf("\n");return 0;
}
运行结果:

📌排序char类型数组
代码展示:
#include <stdio.h>
#include <stdlib.h>int compare_char(const void* a, const void* b)
{return *(char*)a - *(char*)b; //强制转换为char类型并解引用
}int main()
{char arr[] = { 'f', 'e','d','b','a','c' };int sz = sizeof(arr) / sizeof(arr[0]);printf("排序前:");for (int i = 0; i < sz; i++){printf("%c ", arr[i]);}//排序qsort(arr, sz, sizeof(arr[0]), compare_char);//打印printf("\n排序后:");for (int i = 0; i < sz; i++){printf("%c ", arr[i]);}printf("\n");return 0;
}
运行结果:

📌排序浮点型数组
代码展示:
#include <stdio.h>
#include <stdlib.h>int compare_float(const void* a, const void* b)
{float num1 = *(float*)a;float num2 = *(float*)b;if (num1 < num2) return -1;if (num1 > num2) return 1;return 0;
}int main()
{float arr[] = { 5.2 , 2.5 , 3.14 , 1.5 };int sz = sizeof(arr) / sizeof(arr[0]);printf("排序前:");for (int i = 0; i < sz; i++){printf("%f ", arr[i]);}//排序qsort(arr, sz, sizeof(arr[0]), compare_float);//打印printf("\n排序后:");for (int i = 0; i < sz; i++){printf("%f ", arr[i]);}printf("\n");return 0;
}
注意:
- 由于浮点数的精度和范围有限,返回差值可能导致精度丢失和不稳定的结果,特别是在极端情况下。因此,在处理浮点数时,使用差值可能会引发一些问题。
- 为了确保排序的稳定性和正确性,最好的做法是显式地使用 if 语句来比较元素的值,并返回 -1、0 或 1,以确保在各种情况下都能获得正确的比较结果。
运行结果:

📌排序结构体类型数组
代码展示:
1. 【按姓名来排序】
//按姓名来排序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct Student
{char name[20];int age;
}stu;int compare_name(const void* a, const void* b)
{return strcmp( ((stu*)a)->name, ((stu*)b)->name );//比较字符大小使用strcmp函数//strcmp函数返回值与compare_name函数一致
}int main()
{stu s[3] = { {"张三",20},{"李四",18},{"王五",25} };int sz = sizeof(s) / sizeof(s[0]);printf("排序前:");for (int i = 0; i < sz; i++){printf("%s %d", s[i].name, s[i].age);if (i < sz - 1)printf(" | ");}//排序qsort(s, sz, sizeof(s[0]), compare_name);//打印printf("\n排序后:");for (int i = 0; i < sz; i++){printf("%s %d", s[i].name, s[i].age);if (i < sz - 1)printf(" | ");}printf("\n");return 0;
}
运行结果:

2. 【按年龄来排序】
代码展示:
//按年龄来排序
#include <stdio.h>
#include <stdlib.h>
typedef struct Student
{char name[20];int age;
}stu;int compare_age(const void* a, const void* b)
{return (((stu*)a)->age - ((stu*)b)->age);
}int main()
{stu s[3] = { {"张三",20},{"李四",18},{"王五",25} };int sz = sizeof(s) / sizeof(s[0]);printf("排序前:");for (int i = 0; i < sz; i++){printf("%s %d", s[i].name, s[i].age);if (i < sz - 1)printf(" | ");}//排序qsort(s, sz, sizeof(s[0]), compare_age);//打印printf("\n排序后:");for (int i = 0; i < sz; i++){printf("%s %d", s[i].name, s[i].age);if (i < sz - 1)printf(" | ");}printf("\n");return 0;
}
运行结果:

四、模拟实现qsort函数
这里我是基于冒泡函数的思路来实现qsort函数的(实际上qsort函数的排序思路是快速排序)冒泡排序的设计在本篇文末
🧩冒泡排序
#include<stdio.h>
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 - i - 1; j++){if (arr[j] > arr[j + 1]){int tmp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = tmp;}}}
}
int main()
{int arr[] = { 3,1,7,5,8,9,0,2,4,6 };int sz = sizeof(arr) / sizeof(arr[0]);printf("冒泡排序前:\n");for (int i = 0; i < sz; i++){printf("%d ", arr[i]);}//冒泡排序bubble_sort(arr, sz);printf("\n冒泡排序后:\n");for (int i = 0; i < sz; i++){printf("%d ", arr[i]);}printf("\n");return 0;
}
运行结果:

- 这里我们发现bubble_sort函数中用来接收待排序数组首元素地址的
指针arr已经被写死了,是int*类型,这就表它只能对整型数组进行排序。- 其次函数内部对数组元素的比较和交换只适用于
int类型的数据。
现在将利用冒泡排序来实现qsort函数,让它能排序任意类型的数据,该怎么做呢?
- 首先我们知道qsort函数的创作者,他并不知道我们将来需要排序什么类型的数组,但是呢?他却通过qsort函数实现了各种类型数组的排序,这是怎么做到的呢?这就得益于这个函数的
4个参数了。 - 因此,只要我们给qsort函数提供
待排序数组首元素的地址,数组中元素的个数,数组中每个元素所占内存空间的字节大小,以及一个比较函数就能实现对这个数组的排序。所以我们也可以通过这些参数来用冒泡排序的思想实现对任意类型数组的排序。
🧩bubble_sort函数(模拟实现的qsort函数)
值得注意的是,这里说的利用冒泡排序来实现
qsort函数,仅仅是实现了qsort函数可以对任意类型的数组进行排序这一特点,并不是说实现了qsort函数的底层原理,qsort的底层其实是通过快速排序来实现的。
//利用冒泡排序实现qsort
void Swap(char* e1, char* e2, size_t width)
{int i = 0;for (i = 0; i < width; i++){char tmp = *e1;*e1 = *e2;*e2 = tmp;e1++;e2++;}
}
//注意:这里的compar函数需要根据待排序的类型来书写
void bubble_sort(void* arr, int sz, size_t width, int(*compar)(const void* e1, const void* e2))
//第一个参数 - 用来接收待排序数组的首元素地址,因为待排序的数组元素类型不确定,所以形参数组用void*来接收
//第二个参数 - 用来接收数组元素个数
//第三个参数 - 用来接收数组中每个元素的大小,单位是字节
//第四个参数 - 用来接收一个比较函数,根据待排序数组元素的类型来传递对应类型的比较函数
{int i = 0;//趟数for (i = 0; i < sz - 1; i++){int flag = 1;//假设数组是排序好的//一趟冒泡排序的过程int j = 0;for (j = 0; j < sz - 1 - i; j++){if (compar((char*)arr + j * width, (char*)arr + (j + 1) * width) > 0)//因为我们并不知道数组元素的类型,所以需要将元素转化为最小的char*类型,//即把arr强转为char*类型,arr就可以正常使用,且char*与width配合能访问到任意类型任意位置处的数组元素//char类型指针+1只会跳过一个字节,+ j*width表示跳过j个元素{//交换//由于这里的数组名已经被强转为char类型的指针//所以要交换数组中的元素,就只能一个字节一个字节进行交换Swap((char*)arr + j * width, (char*)arr + (j + 1) * width, width);//前两个参数是待交换元素的地址,第三个参数是待交换元素的所占字节的大小flag = 0;//如果数组元素进行交换了,说明数组还没有排好序} }if (flag == 1)//如果没有再交换数组元素,就说明数组已经排好序{break;}}
}
🚩Swap函数剖析
Swap 函数用于交换两个元素的内容,它接受三个参数,这三个参数的作用如下:
void Swap(void* e1, void* e2, size_t width);
-
void* e1: 指向第一个待交换元素的指针。由于数组的元素类型是未知的,所以使用void*类型来表示元素的指针。在函数内部,你需要将其转换为正确的类型,以便进行元素交换。 -
void* e2: 指向第二个待交换元素的指针。同样,你需要在函数内部将其转换为正确的类型,以便进行交换操作。 -
size_t width: 表示每个元素所占的字节数。由于元素类型未知,但在bubble_sort函数中有提供,所以通过这个参数确保在进行元素交换时能够正确地按字节进行操作。
- 在
Swap函数内部,通过使用width参数,以字节为单位逐个交换两个元素的内容。这种设计使得Swap函数在不知道元素实际类型的情况下,仍能够正确地交换元素内容。- 虽然在实际代码中,可能会使用更高级的语言特性来进行元素交换(例如 C++ 中的模板函数或 C 中的宏),但是在这个示例中,通过使用
void*指针和字节级的操作,实现了一个通用的元素交换函数。
🧩利用bubble_sort函数排序整型数组
代码展示:
#include<stdio.h>
//利用bubble_sort函数排序整型数组
void Swap(char* e1, char* e2, size_t width)
{int i = 0;for (i = 0; i < width; i++){char tmp = *e1;*e1 = *e2;*e2 = tmp;e1++;e2++;}
}void bubble_sort(void* arr, int sz, size_t width, int(*compar)(const void* e1, const void* e2))
{int i = 0;for (i = 0; i < sz - 1; i++){int flag = 1;//假设数组是排序好的int j = 0;for (j = 0; j < sz - 1 - i; j++){if (compar((char*)arr + j * width, (char*)arr + (j + 1) * width) > 0)//实现升序{Swap((char*)arr + j * width, (char*)arr + (j + 1) * width, width);flag = 0;//如果数组元素进行交换了,说明数组还没有排好序}}if (flag == 1)//如果没有再交换数组元素,就说明数组已经排好序{break;}}
}
//比较函数
int cmp_int(const void* e1, const void* e2)
{return *(int*)e1 - *(int*)e2;
}
//主函数
int main()
{int arr[] = { 3,1,7,5,8,9,0,2,4,6 };int sz = sizeof(arr) / sizeof(arr[0]);printf("排序前:\n");for (int i = 0; i < sz; i++){printf("%d ", arr[i]);}//排序bubble_sort(arr, sz, sizeof(int), cmp_int);printf("\n排序后:\n");for (int i = 0; i < sz; i++){printf("%d ", arr[i]);}printf("\n");return 0;
}
运行结果:

🧩利用bubble_sort函数排序结构体数组
1. 【按姓名来排序】
代码展示:
//按姓名来排序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//利用bubble_sort函数排序结构体数组
void Swap(char* e1, char* e2, size_t width)
{int i = 0;for (i = 0; i < width; i++){char tmp = *e1;*e1 = *e2;*e2 = tmp;e1++;e2++;}
}void bubble_sort(void* arr, int sz, size_t width, int(*compar)(const void* e1, const void* e2))
{int i = 0;for (i = 0; i < sz - 1; i++){int flag = 1;//假设数组是排序好的int j = 0;for (j = 0; j < sz - 1 - i; j++){if (compar((char*)arr + j * width, (char*)arr + (j + 1) * width) > 0)//实现升序{Swap((char*)arr + j * width, (char*)arr + (j + 1) * width, width);flag = 0;//如果数组元素进行交换了,说明数组还没有排好序}}if (flag == 1)//如果没有再交换数组元素,就说明数组已经排好序{break;}}
}
//声明一个结构体,并重命名为stu
typedef struct student
{char name[20];int age;
}stu;
//比较函数
int compare_name(const void* a, const void* b)
{return strcmp( ((stu*)a)->name, ((stu*)b)->name );//strcmp函数返回值与compare_name函数一致
}int main()
{stu s[3] = { {"张三",20},{"李四",18},{"王五",25} };int sz = sizeof(s) / sizeof(s[0]);printf("排序前:");for (int i = 0; i < sz; i++){printf("%s %d", s[i].name, s[i].age);if (i < sz - 1)printf(" | ");}//排序bubble_sort(s, sz, sizeof(s[0]), compare_name);//打印printf("\n排序后:");for (int i = 0; i < sz; i++){printf("%s %d", s[i].name, s[i].age);if (i < sz - 1)printf(" | ");}printf("\n");return 0;
}
运行结果:

2. 【按年龄来排序】
代码展示:
//按年龄来排序
#include <stdio.h>
#include <stdlib.h>
//利用bubble_sort函数排序结构体数组
void Swap(char* e1, char* e2, size_t width)
{int i = 0;for (i = 0; i < width; i++){char tmp = *e1;*e1 = *e2;*e2 = tmp;e1++;e2++;}
}void bubble_sort(void* arr, int sz, size_t width, int(*compar)(const void* e1, const void* e2))
{int i = 0;for (i = 0; i < sz - 1; i++){int flag = 1;//假设数组是排序好的int j = 0;for (j = 0; j < sz - 1 - i; j++){if (compar((char*)arr + j * width, (char*)arr + (j + 1) * width) > 0)//实现升序{Swap((char*)arr + j * width, (char*)arr + (j + 1) * width, width);flag = 0;//如果数组元素进行交换了,说明数组还没有排好序}}if (flag == 1)//如果没有再交换数组元素,就说明数组已经排好序{break;}}
}
//声明一个结构体,并重命名为stu
typedef struct student
{char name[20];int age;
}stu;
//比较函数
int compare_age(const void* a, const void* b)
{return (((stu*)a)->age - ((stu*)b)->age);
}int main()
{stu s[3] = { {"张三",20},{"李四",18},{"王五",25} };int sz = sizeof(s) / sizeof(s[0]);printf("排序前:");for (int i = 0; i < sz; i++){printf("%s %d", s[i].name, s[i].age);if (i < sz - 1)printf(" | ");}//排序bubble_sort(s, sz, sizeof(s[0]), compare_age);//打印printf("\n排序后:");for (int i = 0; i < sz; i++){printf("%s %d", s[i].name, s[i].age);if (i < sz - 1)printf(" | ");}printf("\n");return 0;
}
运行结果:

总结
回调函数和 qsort 是 C 语言编程中重要的概念,能够提供强大的灵活性和功能。通过理解回调函数的概念,我们可以将特定行为作为参数传递给其他函数,实现代码的模块化和解耦合。qsort 则为数组排序提供了便利,允许我们自定义比较逻辑以满足不同的需求。
通过以上的介绍和模拟实现,希望初学者们能够更好地理解回调函数和 qsort 的核心概念,为日后的编程实践打下坚实的基础。无论是构建灵活的程序结构还是优化代码性能,这些概念都将成为你编程工具箱中不可或缺的工具。
🔥今天的分享就到这里, 如果觉得博主的文章还不错的话,
请👍三连支持一下博主哦🤞

相关文章:
深入理解回调函数qsort:从入门到模拟实现
🍁博客主页:江池俊的博客 💫收录专栏:C语言进阶之路 💡代码仓库:江池俊的代码仓库 🎪我的社区:GeekHub 🎉欢迎大家点赞👍评论📝收藏⭐ 文章目录 前…...
【Git基础】获取远程仓库
我们通常从远程服务器克隆一个Git仓库或者将本地仓库初始化为Git仓库。 1 从远程服务器克隆一个Git仓库 $ git clone https://github.com/your-username/your-repo-name你可以自定义其仓库名称: $ git clone https://github.com/your-username/your-repo-name cu…...
chatGPT界面
效果图: 代码: <!DOCTYPE html> <html> <head><title>复选框样式示例</title> </head> <style>* {padding:0;margin: 0;}.chatpdf{display: flex;height: 100vh;flex-direction: row;}.chatpdf .pannel{widt…...
windows一键启动jupyter
windows一键启动jupyter jupyter简介 Jupyter是一个开源的交互式计算环境,主要用于数据分析、数据可视化和科学计算。它的名字来源于三种编程语言的缩写:Julia、Python和R,这三种语言都可以在Jupyter环境中运行。如果您想进行数据分析、科学…...
树形结构的快速生成
背景 相信大家都遇到过树形结构,像是文件列表、多级菜单、评论区的设计等等,我们都发现它有很多层级,第一级可以有多个,下边的每一个层级也可以有多个;有的可以设计成无限层级的,有的只能设计成两级。那么…...
Android笔记(二十七):自定义Dialog实现居中Toast
背景 记录实现符合项目需求的Toast弹窗 具体实现 class MyTipDialog private constructor(val context: Activity): Dialog(context, R.style.MyTipTheme) {val resId ObservableField(0)private val mainHandler Handler(Looper.getMainLooper())init {setCanceledOnTouc…...
css实现文字的渐变,适合大屏
1 在全局写一个全局样式,文字渐变 2 在组件中使用 CSS3利用-webkit-background-clip: text;实现文字渐变效果_css如何把盒子底部的文字变成透明渐变_I俩月亮的博客-CSDN博客 CSS 如何实现文字渐变色 ?_css字体颜色渐变_一个水瓶座程序猿.的博客-CSDN博客…...
软考高级系统架构设计师系列论文八十七:论企业应用集成
软考高级系统架构设计师系列论文八十七:论企业应用集成 一、企业应用集成相关知识点二、摘要三、正文四、总结一、企业应用集成相关知识点 软考高级系统架构设计师系列之:企业集成平台技术的应用和架构设计二、摘要 本文讨论了某公司的应用系统集成项目。某公司为了应对市场变…...
C++设计模式之适配器模式
一、适配器模式 适配器模式(Adapter Pattern)是一种结构型设计模式,用于将一个类的接口转换成另一个类所期望的接口,以便两个类能够协同工作。 适配器模式可以解决现有类接口与所需接口不匹配的问题,使得原本因接口不…...
山西电力市场日前价格预测【2023-08-24】
日前价格预测 预测明日(2023-08-24)山西电力市场全天平均日前电价为319.98元/MWh。其中,最高日前电价为370.78元/MWh,预计出现在19: 30。最低日前电价为272.42元/MWh,预计出现在12: 45。 价差方向预测 1: 实…...
一文速学-让神经网络不再神秘,一天速学神经网络基础(一)
前言 思索了很久到底要不要出深度学习内容,毕竟在数学建模专栏里边的机器学习内容还有一大半算法没有更新,很多坑都没有填满,而且现在深度学习的文章和学习课程都十分的多,我考虑了很久决定还是得出神经网络系列文章,…...
百度Q2财报:营收341亿元实现加速增长,净利润高速增长44%,增长强劲全线重构
北京时间8月22日,百度发布了截至2023年6月30日的第二季度未经审计的财务报告。第二季度,百度实现营收341亿元,同比增长15%;归属百度的净利润(non-GAAP)达到80亿元,同比增长44%。营收和利润双双实…...
ARM DIY(二)配置晶振频率
文章目录 前言串口乱码问题定位内核修改晶振频率uboot 修改晶振频率番外篇 前言 上篇文章《ARM DIY 硬件调试》介绍了 DIY ARM 板的基础硬件焊接,包括电源、SOC、SD 卡座等,板子已经可以跑起来了。 但是发现串口乱码,今天就来解决串口乱码问…...
高等数学:线性代数-第三章
文章目录 第3章 矩阵的初等变换与线性方程组3.1 矩阵的初等变换3.2 矩阵的秩3.3 方程组的解 第3章 矩阵的初等变换与线性方程组 3.1 矩阵的初等变换 矩阵的初等变换 下面三种变换称为矩阵的初等变换 对换两行(列),记作 r i ↔ r j ( c i …...
深入理解 SQL 注入攻击原理与防御措施
系列文章目录 文章目录 系列文章目录前言一、SQL 注入的原理二、防御 SQL 注入攻击的措施1. 使用参数化查询2.输入验证与过滤3.最小权限原则4.不要动态拼接 SQL5.ORM 框架6.转义特殊字符三、实例演示总结前言 SQL 注入是一种常见的网络攻击方式,攻击者通过在输入框等用户交互…...
QT5.12.12通过ODBC连接到GBase 8s数据库(CentOS)
本示例使用的环境如下: 硬件平台:x86_64(amd64)操作系统:CentOS 7.8 2003数据库版本(含CSDK):GBase 8s V8.8 3.0.0_1 为什么使用QT 5.12.10?该版本包含QODBC。 1&#…...
爱校对发布全新PDF校对工具,为用户带来更为便捷的校正体验
随着数字化文档使用的普及,PDF格式已经成为最为广泛使用的文件格式之一。为满足广大用户对于高效、准确PDF文档校对的需求,爱校对团队经过深入研发,正式推出全新的PDF校对工具! 这一全新工具针对PDF文件格式进行了深度优化&#…...
记录protocol buffers Mac安装
使用brew安装最新的protobuf 在Mac 上安装,使用brew 可以安装最新的protobuf。这个也比较简单,简单说一下。 首先先检查一下是否安装了brew。如果没有安装brew的话,请先安装brew.可以通过brew --version来检查 使用brew install protobuf 来…...
基于Jenkins自动打包并部署docker、PHP环境,ansible部署-------从小白到大神之路之学习运维第86天
第四阶段提升 时 间:2023年8月23日 参加人:全班人员 内 容: 基于Jenkins部署docker、PHP环境 目录 一、环境部署 (一)实验环境,服务器设置 (二)所有主机关闭防火墙和selinu…...
【附安装包】Midas Civil2019安装教程
软件下载 软件:Midas Civil版本:2019语言:简体中文大小:868.36M安装环境:Win11/Win10/Win8/Win7硬件要求:CPU2.5GHz 内存4G(或更高)下载通道①百度网盘丨64位下载链接:https://pan.…...
手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
线程与协程
1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指:像函数调用/返回一样轻量地完成任务切换。 举例说明: 当你在程序中写一个函数调用: funcA() 然后 funcA 执行完后返回&…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...
【C++进阶篇】智能指针
C内存管理终极指南:智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...
Proxmox Mail Gateway安装指南:从零开始配置高效邮件过滤系统
💝💝💝欢迎莅临我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:「storms…...
Qt 事件处理中 return 的深入解析
Qt 事件处理中 return 的深入解析 在 Qt 事件处理中,return 语句的使用是另一个关键概念,它与 event->accept()/event->ignore() 密切相关但作用不同。让我们详细分析一下它们之间的关系和工作原理。 核心区别:不同层级的事件处理 方…...
mac:大模型系列测试
0 MAC 前几天经过学生优惠以及国补17K入手了mac studio,然后这两天亲自测试其模型行运用能力如何,是否支持微调、推理速度等能力。下面进入正文。 1 mac 与 unsloth 按照下面的进行安装以及测试,是可以跑通文章里面的代码。训练速度也是很快的。 注意…...
