[MSDN]如何利用qsort对CArray的继承类进行排序

HOWTO: Quick Sorting Using MFC CArray-Derived Classes

ID: Q216858


The information in this article applies to:

  • Microsoft Visual C++, 32-bit Editions, versions 5.0, 6.0


 

SUMMARY

This article shows you how to quick-sort an MFC CArray-derived class. The code below depends only on the MFC and Visual C++ run-time library.


 

MORE INFORMATION

The Visual C++ run-time library (MSVCRT) implements the quick-sort function, qsort, as follows:


void qsort( void *base, size_t num, size_t width, int (__cdecl *compare )(const void *elem1, const void *elem2 ) ); 


The following example shows you how to use this function to sort an MFC CArray class. Although the following example uses a CString array, you can easily customize for other MFC CArray-derived classes, such as CByteArray, CDWordArray, CObArray, CPtrArray, CUintArray, and CWordArray.

Steps to Implement Sorting


 

  1. Derive your array data class from one of the CArray-derived classes. In our example, we use CStringArray as the base class for our class. The following is the declaration in the header file:

    
    class CSortableStringArray : public CStringArray
    {
    public:
    protected:
    }; 


  2. To the public section of your class add the following function:
    
    void Sort(STRINGCOMPAREFN pfnCompare = Compare); 


  3. To the protected section of your class add the following static function:
    
    static int __cdecl Compare(const CString * pstr1, const CString * pstr2); 


  4. Now add the following two type defs above the declaration of your class. These typedefs later help us in passing pointers of the two functions that we declared in steps 2 and 3 to the Visual C++ run-time's qsort function:

    
    typedef int (__cdecl *GENERICCOMPAREFN)(const void * elem1, const void * elem2);
    typedef int (__cdecl *STRINGCOMPAREFN)(const CString * elem1, const CString * elem2); 


  5. In your .cpp file, implement the two functions that you declared earlier:
    
    // 
    // SortableStringArray.cpp
    // 
    
    int CSortableStringArray::Compare(const CString * pstr1, const CString * pstr2)
    {
    	ASSERT(pstr1);
    	ASSERT(pstr2);
    	return pstr1->Compare(*pstr2);
    }
    
    void CSortableStringArray::Sort(STRINGCOMPAREFN pfnCompare /*= CSortedStringArray::Compare */)
    {
    	CString * prgstr = GetData();
    	qsort(prgstr,GetSize(),sizeof(CString),(GENERICCOMPAREFN)pfnCompare);
    } 


That is it. Now let's look at the contents of the entire file:


// 
// SortableStringArray.h
// 

typedef int (__cdecl *GENERICCOMPAREFN)(const void * elem1, const void * elem2);
typedef int (__cdecl *STRINGCOMPAREFN)(const CString * elem1, const CString * elem2);

class CSortableStringArray : public CStringArray
{
public:
	void Sort(STRINGCOMPAREFN pfnCompare = Compare);

protected:
	static int __cdecl Compare(const CString * pstr1, const CString * pstr2);
};

// 
// SortableStringArray.cpp
// 

#include "SortableStringArray.h"

int CSortableStringArray::Compare(const CString * pstr1, const CString * pstr2)
{
	ASSERT(pstr1);
	ASSERT(pstr2);
	return pstr1->Compare(*pstr2);
}

void CSortableStringArray::Sort(STRINGCOMPAREFN pfnCompare /*= CSortedStringArray::Compare */)
{
	CString * prgstr = GetData();
	qsort(prgstr,GetSize(),sizeof(CString),(GENERICCOMPAREFN)pfnCompare);
} 



To use the array in your code, just declare it and call the Sort() function. The following example uses this array:


	srand( (unsigned)time( NULL ) ); // Generate seed for rand().
	CSortableStringArray arr;
	CString str;
	for (int i=0; i< 1000;i++)
	{
		str.Format("%6d", rand());// Get a random number string.		
		arr.Add(str);
		TRACE("%s/n", (LPCTSTR)str);
	}
	long ltim=GetTickCount();
	arr.Sort();
	for (i=0; i< 1000;i++)
	{
		TRACE("%s/n", (LPCTSTR)arr[i]);
	}

	TRACE("Time took= %li/n", GetTickCount()-ltim); 



NOTE: To implement a CArray type other than CString, just derive your class from the other array types and modify the Sort() and the Compare() functions accordingly.

NOTE: Quick Sort is a more efficient mechanism of sorting than the bubble sort used in the Microsoft Knowledge Base article, "How to Sort a CStringArray in MFC" (Q120961)


 

REFERENCES

For additional information about the bubble sort method, please see the following article in the Microsoft Knowledge Base:

Q120961 How to Sort a CStringArray in MFC

Additional query words:

Keywords : kbDatabase kbMFC kbODBC kbVC500 kbVC600
Version : winnt:5.0,6.0
Platform : winnt
Issue type : kbhowto


Last Reviewed: February 4, 2000
© 2000 Microsoft Corporation. All rights reserved. Terms of Use.

<think>我们被要求使用qsort函数对结构体变量进行多重排序。在C语言中,qsort函数是标准库中的一个快速排序函数,可以对任意型的数据进行排序。我们需要为qsort提供一个比较函数,该函数能够根据我们的排序规则比较两个元素。 对于结构体的多重排序,指的是先根据一个字段排序,如果该字段相等,则根据第二个字段排序,以此推。 步骤: 1. 定义结构体。 2. 编写比较函数,该函数需要满足qsort的要求:接收两个const void*指针,返回负数、0或正数。 3. 在比较函数内部,将void*指针转换为结构体指针。 4. 比较函数中,先比较第一个关键字段,如果不相等则返回比较结果;如果相等,则继续比较下一个字段。 假设我们有一个结构体Person,包含姓名(name)、年龄(age)和分数(score)三个字段。我们想先按分数降序排序,分数相同按年龄升序排序,年龄相同再按姓名升序排序。 注意:qsort的比较函数返回值的含义: - 负数:第一个参数应该排在第二个参数前面 - 0:相等 - 正数:第一个参数应该排在第二个参数后面 因此,对于降序排序,我们可以用第二个参数减第一个参数(如果比较的是数值);对于升序,用第一个参数减第二个参数。 但是要注意,字符串比较使用strcmp,它返回负数、0、正数,分别表示第一个字符串小于、等于、大于第二个字符串。而我们要的升序就是自然顺序,所以直接使用strcmp的结果(注意:升序时,如果第一个字符串小于第二个,我们返回负数,这样第一个参数就会排在前面,符合升序)。 具体比较函数示例: 假设结构体定义: typedef struct { char name[50]; int age; double score; } Person; 排序要求: 1. 分数(score)降序:即高分数在前,低分数在后。 2. 相同分数,年龄(age)升序:即年龄小的在前。 3. 相同年龄,姓名(name)升序:按字典顺序。 比较函数: int compare_person(const void *a, const void *b) { const Person *pa = (const Person *)a; const Person *pb = (const Person *)b; // 先比较分数(降序) if (pa->score > pb->score) return -1; // 注意:降序,所以pa分数高时返回负,使pa排在pb前面 else if (pa->score < pb->score) return 1; // 分数相同,比较年龄(升序) if (pa->age < pb->age) return -1; else if (pa->age > pb->age) return 1; // 年龄也相同,比较姓名(升序) return strcmp(pa->name, pb->name); } 但是注意:直接对浮点数进行相等比较可能有精度问题,但这里作为示例我们忽略。实际中,浮点数比较相等可以用一个很小的阈值。 另一种常见的写法(避免使用多个if-else): int compare_person(const void *a, const void *b) { const Person *pa = (const Person *)a; const Person *pb = (const Person *)b; // 分数降序:由于qsort要求,我们通过交换比较顺序来实现降序 if (pa->score != pb->score) { // 注意:这里我们使用pb->score - pa->score,但因为浮点数,所以不能用减法,而是用比较 // 我们可以这样:如果pa->score > pb->score,我们返回负数(表示a排在b前面),即降序 // 或者用:return (pb->score > pa->score) ? 1 : -1; 但这样在相等时已经判断了,所以这里不会相等 // 更直接:降序时,我们希望a比b大时,a排前面,所以我们返回负数;a比b小时,返回正数。 // 由于浮点数减法可能截断,我们可以: double diff = pb->score - pa->score; if (diff > 0) return 1; else if (diff < 0) return -1; } // 分数相同,比较年龄升序 if (pa->age != pb->age) { return pa->age - pb->age; // 升序:a年龄小则返回负,排在前面 } // 年龄相同,比较姓名升序 return strcmp(pa->name, pb->name); } 但是要注意,浮点数的减法可能导致精度损失,而且如果两个浮点数非常接近,减法可能由于舍入而得到0(但实际上不相等)。因此,更安全的方式是比较浮点数时使用阈值。 改进浮点数比较: #include <math.h> #define EPS 1e-9 int compare_person(const void *a, const void *b) { const Person *pa = (const Person *)a; const Person *pb = (const Person *)b; // 比较浮点数:使用阈值 if (fabs(pa->score - pb->score) > EPS) { // 降序:如果pa->score大于pb->score(超过阈值),则返回负;否则返回正 if (pa->score > pb->score) return -1; else return 1; } // 分数相同(在阈值内),比较年龄 if (pa->age != pb->age) { return pa->age - pb->age; // 升序 } return strcmp(pa->name, pb->name); } 但是注意,浮点数比较的阈值选择要根据实际情况。 另外,在比较年龄时,直接相减可能会导致整数溢出(虽然年龄一般不会很大,但作为通用方法要注意)。更安全的方式是: if (pa->age < pb->age) return -1; else if (pa->age > pb->age) return 1; 因此,我们可以这样写: int compare_person(const void *a, const void *b) { const Person *pa = (const Person *)a; const Person *pb = (const Person *)b; // 比较分数(降序) if (fabs(pa->score - pb->score) > EPS) { // 注意:降序,所以当pa->score > pb->score时,返回负;否则返回正 return (pb->score > pa->score) ? 1 : -1; } // 分数相同,比较年龄(升序) if (pa->age != pb->age) { return (pa->age < pb->age) ? -1 : 1; } // 年龄相同,比较姓名(升序) return strcmp(pa->name, pb->name); } 然而,上面的浮点数比较部分,如果pb->score大于pa->score,我们返回1,表示a应该排在b后面;如果pa->score大于pb->score,我们返回-1,表示a排在b前面。这满足了降序。 但是,在浮点数比较中,我们用了阈值,那么当两个浮点数在阈值内相等时,我们跳过分数比较,进入下一个字段。 最后,我们使用qsort函数: qsort(array, n, sizeof(Person), compare_person); 注意:比较函数的原型必须是int (*)(const void *, const void *)。 总结:多重排序的关键就是在比较函数中依次比较各个字段,如果当前字段能够决定顺序就返回结果,否则继续比较下一个字段。 下面我们给出一个完整的例子: 示例代码: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <math.h> #define EPS 1e-9 typedef struct { char name[50]; int age; double score; } Person; int compare_person(const void *a, const void *b) { const Person *pa = (const Person *)a; const Person *pb = (const Person *)b; // 1. 按score降序 double score_diff = pb->score - pa->score; if (fabs(score_diff) > EPS) { return (score_diff > 0) ? 1 : -1; } // 2. 按age升序 if (pa->age != pb->age) { return (pa->age < pb->age) ? -1 : 1; } // 3. 按name升序 return strcmp(pa->name, pb->name); } int main() { Person people[] = { {"Alice", 25, 85.5}, {"Bob", 25, 85.5}, {"Charlie", 30, 90.0}, {"David", 20, 90.0} }; int n = sizeof(people) / sizeof(people[0]); qsort(people, n, sizeof(Person), compare_person); for (int i = 0; i < n; i++) { printf("Name: %s, Age: %d, Score: %.1f\n", people[i].name, people[i].age, people[i].score); } return 0; } 运行结果: Name: Charlie, Age: 30, Score: 90.0 // 分数最高,但年龄最大(降序中相同分数按年龄升序?注意:这里相同分数按年龄升序,所以年龄小的应该在前。但是这里有两个90.0,所以接下来按年龄升序,则David(20)应该排在Charlie(30)前面。但是我们的代码中,分数相同按年龄升序,所以David应该排在Charlie前面。 Name: David, Age: 20, Score: 90.0 // 正确顺序应该是David在前,然后是Charlie Name: Alice, Age: 25, Score: 85.5 Name: Bob, Age: 25, Score: 85.5 但实际上,上面的代码在比较90.0的两个元素时,因为David的age(20)小于Charlie(30),所以David应该排在Charlie前面。所以输出顺序应该是David先,Charlie后。 因此,我们需要修正:在比较函数中,对于年龄升序,当David(20)和Charlie(30)比较时,David的age小,我们返回-1,表示David应该排在Charlie前面。所以上面的比较函数在年龄升序部分返回-1,这样David就会排在Charlie前面。 但是上面的代码中,当比较David和Charlie时,因为分数相同(90.0),进入年龄比较。David的age=20 < Charlie的age=30,所以返回-1,那么David会排在Charlie前面。所以输出应该是: David, 20, 90.0 Charlie, 30, 90.0 ... 因此,我们上面的代码逻辑正确。 但是注意:在浮点数比较部分,我们使用pb->score - pa->score的差,然后判断正负。这里我们用了减法,但是浮点数减法可能有精度问题,所以我们用阈值。但是更常见的方式是直接比较: if (pa->score > pb->score + EPS) // 说明pa->score > pb->score,则应该排在前面(降序)所以返回-1 else if (pa->score < pb->score - EPS) // 返回1 // 否则视为相等 因此,也可以这样写: if (pa->score > pb->score + EPS) { return -1; } else if (pa->score < pb->score - EPS) { return 1; } 这样更清晰。 修改后的比较函数: int compare_person(const void *a, const void *b) { const Person *pa = (const Person *)a; const Person *pb = (const Person *)b; // 1. 按score降序 if (pa->score > pb->score + EPS) { return -1; // pa的score大于pb,降序,所以pa排pb前面 } else if (pa->score < pb->score - EPS) { return 1; } // 2. 按age升序 if (pa->age < pb->age) { return -1; } else if (pa->age > pb->age) { return 1; } // 3. 按name升序 return strcmp(pa->name, pb->name); } 这样逻辑更清晰。 注意:浮点数的EPS要根据实际情况选择。 另外,在比较整数时,我们直接使用大小比较,不会引入精度问题。 最后,关于结构体排序中的内存对齐和填充问题:一般不会影响排序,因为我们比较的是实际的数据成员。但是要注意,在结构体中可能有填充字节,这些字节是未初始化的,不能直接使用memcmp比较整个结构体(但我们的比较函数是按成员比较的,所以没问题)。 多重排序的通用方法就是按照优先级顺序逐个字段比较,直到能够区分大小或所有字段都比较完。 因此,使用qsort对结构体进行多重排序的关键在于编写一个合适的比较函数,该函数能够按照指定的优先级和顺序比较结构体的各个字段。 </think>### 使用`qsort`函数对结构体变量进行多重排序的方法 在C语言中,`qsort`函数可通过自定义比较函数实现对结构体的多级排序。核心思路是:**在比较函数中,按优先级依次判断各字段,若当前字段可确定顺序则返回结果,否则继续比较下一字段**。以下是详细步骤和示例: --- ### 步骤说明 1. **定义结构体** 例如包含姓名、年龄、分数的结构体: ```c typedef struct { char name[50]; int age; double score; } Person; ``` 2. **编写比较函数** 遵循优先级规则(如:先按分数降序 → 再按年龄升序 → 最后按姓名升序): ```c #include <math.h> #define EPS 1e-9 // 浮点数比较精度阈值 int compare_person(const void *a, const void *b) { const Person *pa = (const Person*)a; const Person *pb = (const Person*)b; // 1. 按score降序(高优先级) if (fabs(pa->score - pb->score) > EPS) { return (pa->score > pb->score) ? -1 : 1; // 降序 } // 2. 按age升序(中优先级) if (pa->age != pb->age) { return (pa->age < pb->age) ? -1 : 1; // 升序 } // 3. 按name升序(低优先级) return strcmp(pa->name, pb->name); // 字典序升序 } ``` 3. **调用`qsort`排序** ```c Person arr[100]; int n = 100; // 元素数量 qsort(arr, n, sizeof(Person), compare_person); ``` --- ### 关键点解析 1. **浮点数比较** 使用阈值`EPS`避免精度问题:`fabs(a - b) > EPS`替代直接的`a != b`比较。 2. **降序/升序控制** - 降序:`return (a > b) ? -1 : 1` - 升序:`return (a < b) ? -1 : 1` 3. **多级排序逻辑** 依次判断字段,若相等则进入下一级: ```c if (字段1不等) return 结果; else if (字段2不等) return 结果; else return 字段3比较结果; ``` --- ### 示例输出 输入数据: ```c {"Alice", 25, 85.5}, {"Bob", 25, 85.5}, {"Charlie", 30, 90.0}, {"David", 20, 90.0} ``` 排序后输出: ``` David 20 90.0 // 分数最高 → 年龄最小 Charlie 30 90.0 // 分数最高 → 年龄较大 Alice 25 85.5 // 同分数 → 按姓名升序 Bob 25 85.5 ``` --- ### 常见问题 1. **为什么字段优先级顺序很重要?** 排序时会**优先满足高优先级字段**的规则(如示例中分数最高者排最前),同值时才启用次级规则。 2. **如何添加更多排序字段?** 在比较函数中继续扩展逻辑链: ```c if (字段1不等) return 结果; if (字段2不等) return 结果; if (字段3不等) return 结果; // 新增字段 return 字段4结果; ``` 3. **性能注意事项** - 避免在比较函数中进行复杂计算 - 对字符串字段,用`strcmp`而非手动循环比较 - 若数据量极大,考虑优化比较逻辑(如将字段预转换为可快速比较的型) 4. **稳定性问题** `qsort`本身不稳定,若需保留同值元素的原始顺序,可额外记录索引值或改用稳定排序算法(如归并排序)[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值