qsort 関数の実装をシミュレートする
qsort 関数に関する予備知識
折り返し電話
コールバック関数は、関数ポインターを通じて呼び出される関数です。
もし、あんたが関数のポインタ(アドレス)をパラメータとして受け取ります別の関数に渡され、このポインターが指す関数を呼び出すために使用される場合、呼び出される関数はコールバック関数です。コールバック関数は、関数の実装者によって直接呼び出されるのではなく、特定のイベントまたは条件が発生したときに、そのイベントまたは条件に応答するために別のパーティによって呼び出されます。
このまま話すと少し抽象的になるかもしれませんが、以下の実際の事例の中で詳しくご紹介します。>
関数ポインタ型の解析
関数ポインタという名前を聞くと、きっとこれはポインタだろうと思うでしょう。実際、それ以外の場合、ポインタはアドレスの格納に使用されるため、関数ポインタ変数を使用して関数アドレスを格納する必要があり、将来はそのアドレスを介して関数を呼び出すことができます。
では、どうやって関数のアドレスを取得するのでしょうか? コードを書いてみましょう:
デバッグを通じて、関数にアドレスがあることを理解するのは難しくありません。関数名は関数のアドレスです。もちろん、関数のアドレスは関数名を通じて取得することもできます&
。では、どうやって関数のアドレスを取得するのでしょうか?今回は、関数ポインターが導入されていますので、Add
関数を例に挙げてみましょう:
qsort 関数の使用法と関連パラメータ
cplusplus.comの説明を見てみましょう!
(1) 最初のパラメータは、ソートされる最初の要素のアドレスです (ここではbase
アドレスを指します)。要素の型が不明なので、void*
変数名として使用
(2) 2 番目のパラメータはソートする要素の数;
(3) 3 番目のパラメータはソートする各要素のサイズ; (4) 3 番目のパラメータは関数
を指す関数ポインタcompar
2 つの要素を比較できます。この関数は自分で実装する必要があります。関数の戻り値の型が で、パラメータの型が であること
がわかります。認識しなければならないのはcompar
int
const void*
型のパラメータvoid*
のサイズを直接比較することはできないため、比較する前に型変換を実行する必要があります。!!
バブルソートアルゴリズム
元のバブルソートアルゴリズムを見てみましょう:
void bubble_sort(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
int flag = 1;
for (int j = 0; j < sz - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
flag = 0;
}
}
if (flag == 1)
break;
}
}
int main()
{
int arr[] = {
1,5,3,6,8,8,5,9,6,5 };
int len = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, len);
for (int i = 0; i < len; i++)
{
printf("%d ", arr[i]);
}
putchar('\n');
return 0;
}
図:
上の図を通して、何回ループする必要があるかを知っていれば、実際には何も難しいことはありません。2 レベルfor
のループを使用してバブル ソートを実装できます。外側のlen
レベル、内側のlen-i
レベルの循環を覚えておいてください。
シミュレーションの実装方法の紹介
バブル ソート アルゴリズムを理解したところで、それを同様のqsort
関数に改良するにはどうすればよいでしょうか? 実際には、まだ外側の層と記憶層
の 2 層のループです。変更されたのは比較関数です。ソートする要素を直接交換するのは難しいため、ここでは交換関数を使用します。これら 2 つの関数の関数は に変換されました。なぜですか? 特定の要素の型がわからないため、走査を容易にするために、要素を型に変換します。このとき、必ず誰かが、 などのタイプに変身しないのですか?答えは、ソートする要素のサイズにあります。ソートする各要素が構造体であり、それぞれが 9 バイトを占める場合、4 バイトと2 バイトは割り切れず、不完全な交換が発生します。これは、関数が毎回比較対象の要素の最初のバイト アドレスを渡し、合計を計算するたびに 1 つの要素をスキップできるようにするためでもあります。len
len-i
cmp((char*)base + j * width, (char*)base + (j + 1) * width)
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
base
char*
void*
char*
int*
short*
int
short
Swap
j*width
(j+1)*width
void Swap(char* buf1, char* buf2, size_t width)
{
for (int i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++; buf2++;
}
}
したがって、この関数Swap
でバイトが交換されるたびにwidth
1 つの要素が交換され、これはfor
ループで実現できます。
ソースコード
struct Stu
{
char name[20];
int age;
};
//比较数组元素
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
//比较结构体中的年龄
int cmp_str_by_age(const void* e1, const void* e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
//比较结构体中的字符串
int cmp_str_by_name(const void* e1, const void* e2)
{
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
//交换两个元素(因为不知道元素类型,所以以一字节为单位逐个交换,至width大小)
void Swap(char* buf1, char* buf2, size_t width)
{
for (int i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++; buf2++;
}
}
//其中调用函数指针,指向cmp函数
void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void* e1, const void* e2))
{
int i = 0;
for (i = 0; i < sz; i++)
{
int flag = 1;//测试是否已排序完成,提高效率
for (int j = 0; j < sz - 1 - i; j++)
{
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
flag = 0;
}
}
if (flag == 1)
break;
}
}
//整形数组
void test1()
{
int arr1[] = {
1,5,3,6,4,9,2,9,6,5 };
int len = sizeof(arr1) / sizeof(arr1[0]);
bubble_sort(arr1, len, sizeof(arr1[0]), cmp_int);
for (int i = 0; i < len; i++)
{
printf("%d ", arr1[i]);
}
putchar('\n');
}
//结构体数组--比较age,整形
void test2()
{
struct Stu arr2[] = {
{
"zhangsan",16},{
"lisi",37},{
"wangwu",22} };
int len = sizeof(arr2) / sizeof(arr2[0]);
bubble_sort(arr2, len, sizeof(arr2[0]), cmp_str_by_age);
}
//结构体数组--比较name,字符串
void test3()
{
struct Stu arr3[] = {
{
"zhangsan",16},{
"lisi",37},{
"wangwu",22} };
int len = sizeof(arr3) / sizeof(arr3[0]);
bubble_sort(arr3, len, sizeof(arr3[0]), cmp_str_by_name);
struct Stu* prv = arr3;
for (int i = 0; i < len; i++)
{
printf("%s ", prv->name);
prv++;
}
}
int main()
{
//三个测试函数
test1();
//test2();
test3();
return 0;
}