多态与虚函数

多态与虚函数

  • 这是一个经典陷阱。C++ 的访问控制检查在编译期基于指针的静态类型**(基类)。只要基类中该虚函数是 public,即使派生类里把它 override 成了 private,通过基类指针调用时编译器仍然允许,运行时动态绑定到派生类的私有版本——完全可以调用

  • 封装——类的抽象和设计/可见性
  • 继承——基类派生派生类/代码继承
  • 多态——函数重载/?

虚函数

带 virtual 关键字成员函数

  • 用在类定义的函数声明中,写函数体不用
  • 静态成员函数不能是虚函数
  • 基类函数声明,派生类自动虚函数(同名函数)
1
2
3
4
5
6
7
class base{
virtual int get();
//int virtual get(); //二者等价
};
int base::get(){
...
}

多态表现形式

表现形式1

  • 派生类的指针可以赋给基类指针

  • 通过基类指针调用基类和派生类中的同名虚函数

    • 若该指针 指向的是一个基类的对象,被调用的是基类的虚函数(派生类则派生类的虚函数)

      1
      2
      3
      CBase * r= &ODerived;
      r.VirtualFunction();
      // 根据 指针/引用指向的对象类型,来决定调用的函数

image-20260323220142314

表现形式2

  • 派生类的对象可以赋给基类引用

  • 通过基类引用调用基类和派生类中的同名虚函数

    • 若该引用 引用的是一个基类的对象,被调用的是基类的虚函数(派生类则派生类的虚函数)

      1
      2
      3
      CBase & r=ODerived;
      r.VirtualFunction();
      // 根据 指针/引用指向的对象类型,来决定调用的函数

image-20260323220205489

一个小例子

例子一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <iostream>
using namespace std;

// 原始多态示例代码
class A {
public:
virtual void Print() { cout << "A::Print" << endl; }
};

class B : public A {
public:
virtual void Print() { cout << "B::Print" << endl; }
};

class D : public A {
public:
virtual void Print() { cout << "D::Print" << endl; }
};

class E : public B {
public:
virtual void Print() { cout << "E::Print" << endl; }
};

// 修改后的示例代码(派生类 Print 不写 virtual,效果相同)
/*
class A {
public:
virtual void Print() { cout << "A::Print" << endl; }
};

class B : public A {
public:
void Print() { cout << "B::Print" << endl; }
};

class D : public A {
public:
void Print() { cout << "D::Print" << endl; }
};

class E : public B {
public:
void Print() { cout << "E::Print" << endl; }
};
*/

int main() {
A a; B b; E e; D d;
A *pa = &a;
B *pb = &b;
D *pd = &d;
E *pe = &e;

pa->Print(); // 输出: A::Print
pa = pb;
pa->Print(); // 输出: B::Print
pa = pd;
pa->Print(); // 输出: D::Print
pa = pe;
pa->Print(); // 输出: E::Print

return 0;
}

代码说明

  1. 多态核心A 类中 Print 声明为 virtual,派生类重写该函数后,通过基类指针 pa 调用时,会根据指针实际指向的对象类型,自动调用对应类的 Print 函数,这就是运行时多态
  2. virtual 继承特性:基类函数声明为 virtual 后,所有派生类中同名函数自动保持虚函数特性,即使不写 virtual 关键字,多态效果也完全一致
  3. 继承关系E 继承自 BBD 继承自 A,因此 E 也间接继承自 A,可以被 A* 指针指向。

例子二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
using namespace std;

// 基类 myclass
class myclass {
public:
void reset();
// virtual 关键字可写在返回值前或后
virtual void print() {
cout << "myclass::print\n";
}
};

// 基类普通成员函数 reset,内部调用虚函数 print
void myclass::reset() {
print(); // 等价于 this->print(); this 是基类指针,会触发多态
}

// 派生类 derived,继承自 myclass
class derived : public myclass {
public:
// 重写基类虚函数 print
virtual void print() {
cout << "derived::print\n";
}
};

int main() {
myclass b;
derived d;
b.reset(); // 调用 myclass::reset(),内部调用 myclass::print()
d.reset(); // 调用 myclass::reset(),内部调用 derived::print()(多态)
return 0;
}

运行结果

1
2
myclass::print
derived::print

多态的价值

场景手动写 A::多态(virtual)
适用范围只在你明确知道类型时能用哪怕不知道对象类型,也能正确调用
扩展性新增派生类时,要到处改代码新增派生类时,原有代码完全不用改
核心价值语法糖实现开闭原则:对扩展开放,对修改关闭

增强可扩充性

要改代码少

多态的作用

多态的实质

  • 父类定义共同接口,子类不同实现
  • 通过父类以相同方式操作不同子类行为

多态的实现实例

实例1

游戏:6只怪兽、每只怪兽可以攻击、被击后的扣生命值、被击后的后摇(每只怪兽可能不同)

非多态反面案例
基本思路
  • 为每个怪兽编写attackfightbackhurted成员函数
  • Attack:
    • 被攻击怪兽Hurted
    • 被攻击怪兽Fightback
  • Hurted:自身生命值减少
  • FightBack:反击并调用被反击对象的Hurted

传统的实现方式:

  • 怪兽A:攻击其他5个怪兽的函数、被其他5个怪兽攻击后摇(扣生命值)的5个函数
  • 怪兽B:……
  • ……

若增加一个怪兽,则每个怪兽都要修改——平方型代码量增加,代码KPI轻松完成

多态做法
基类派生
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
graph TD
%% 定义节点样式(可选,模拟原图效果)
classDef baseNode fill:#c6e7ff,stroke:#66afe9,stroke-width:2px;
classDef subNode fill:#ffffff,stroke:#999,stroke-width:1px;

%% 父节点
CCreature["CCreature"]:::baseNode

%% 子节点
CDragon["CDragon"]:::subNode
CWolf["CWolf"]:::subNode
CSoldier["CSoldier"]:::subNode
CPhonex["CPhonex"]:::subNode
CAngel["CAngel"]:::subNode

%% 继承关系(父 -> 子)
CCreature --> CDragon
CCreature --> CWolf
CCreature --> CSoldier
CCreature --> CPhonex
CCreature --> CAngel


基类:

1
2
3
4
5
6
7
8
class CCreature{
protected:
int m_nLiveValue,m_nPower;
public:
virtual void Attack(CCreature *pCreature);
virtual void Hurted(int nPower);
virtual void FrightBack(CCreature *pCreature);
};

派生:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class CDragon : public CCreature{
public:
virtual void Attack(CCreature *pCreature);
virtual void Hurted(int nPower);
virtual void FrightBack(CCreature *pCreature);
};

void CDragon::Attack(CCreature *p){
p->Hurted(m_nPower);
p->FightBack(this);
}
void CDragon::Hurted(int nPower){
m_nLifeValue-=nPower;
}
void CDragon::FightBack(CCreature *p){
p->Hurted(m_nPower/2);
}

于是,新怪兽只需要编写新类,不需要专门增加传入新怪物的成员函数

具体使用的时候:

1
2
3
4
5
6
7
CDragon Dragon;
CWolf Wolf;
CGhost Ghost;
CThunderBird Brid;
Dragon.Attack(&Wolf);
...
CGhost::Hurted;

注意:以上采用传指针的方式实现多态,使用引用传入亦可

实例2:几何形体处理程序

需求:输入若干个几何形体的参数,要求按面积排序输出,输出时要指明形状。

  • 输入:第一行是几何形体数目n (≤100),下面n行每行以字母c开头;c为’R’代表矩形,后跟宽和高;c为’C’代表圆,后跟半径;c为‘T’代表三角形,后跟三条边长度。

  • 输出:按面积从小到大输出每个几何形体的种类及面积,格式为“形体名称: 面积”。

  • 示例输入:
    3
    R 3 5
    C 9
    T 3 4 5

  • 示例输出:
    Triangle: 6
    Rectangle: 15
    Circle: 254.34

    实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include <iostream>
#include <stdlib.h> //快速排序函数
#include <math.h> //数学函数
using namespace std;
class CShape {
public:
virtual double Area( ) = 0; //纯虚函数
virtual void PrintInfo( ) = 0;
};
class CRectangle: public CShape {
public:
int w, h;
virtual double Area();
virtual void PrintInfo();
};
class CCircle: public CShape {
public:
int r;
virtual double Area();
virtual void PrintInfo();
};
class CTriangle: public CShape {
public:
int a, b, c;
virtual double Area();
virtual void PrintInfo();
};
double CRectangle::Area() {
return w * h;
}
void CRectangle::PrintInfo() {
cout << "Rectangle:" << Area() << endl;
}
double CCircle::Area() {
return 3.14 * r * r ;
}
void CCircle::PrintInfo() {
cout << "Circle:" << Area() << endl;
}
double CTriangle::Area() {
double p = (a + b + c) / 2.0;
return sqrt(p * (p - a)*(p - b)*(p - c));
}
void CTriangle::PrintInfo() {
cout << "Triangle:" << Area() << endl;
}
CShape * pShapes[100]; //用来存放各种几何形体, 假设不超过100
int MyCompare(const void * s1, const void * s2){
CShape * * p1;
CShape * * p2;
p1 = ( CShape * * ) s1;
p2 = ( CShape * * ) s2;
double a1 = (*p1)->Area(); //多态调用
double a2 = (*p2)->Area();
if( a1 < a2 ) return -1;
else if ( a2 < a1 ) return 1;
else return 0;
}
int main() {
int i; int n;
CRectangle * pr;
CCircle * pc;
CTriangle * pt;
cin >> n;
for( i = 0; i < n; i ++ ) {
char c;
cin >> c;
switch(c) {
case 'R':
pr = new CRectangle();
cin >> pr->w >> pr->h;
pShapes[i] = pr;
break;
case 'C':
pc = new CCircle();
cin >> pc->r;
pShapes[i] = pc;
break;
case 'T':
pt = new CTriangle();
cin >> pt->a >> pt->b >> pt->c;
pShapes[i] = pt;
break;
}
}
qsort(pShapes, n, sizeof(CShape*), MyCompare);
for( i = 0; i <n; i ++ )
pShapes[i]->PrintInfo(); //多态调用
return 0;
}

代码说明

  1. ⚠️纯虚函数与抽象类CShape 包含纯虚函数,是抽象类,仅定义统一接口,不能实例化
  2. 多态调用:基类指针数组存储派生类对象,调用Area()PrintInfo()自动匹配对应类的实现
  3. 排序逻辑:通过qsort+自定义比较函数,利用多态计算面积并排序

优势:添加新的几何形体(如五边形)时,只需从CShape派生出CPentagon类并重写虚函数,再在main的switch中增加一个case即可,其余代码无需修改。

核心技巧:用基类指针数组存放指向各种派生类对象的指针,然后遍历该数组对各个派生类对象做操作,是C++中使用多态的常用做法。

MORE:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Base {
public:
void fun1() { fun2(); }
virtual void fun2() { cout << "Base::fun2()" << endl; }
};
class Derived:public Base {
public:
virtual void fun2() { cout << "Derived:fun2()" << endl; }
};
int main() {
Derived d;
Base * pBase = & d;
pBase->fun1();
return 0;
}

输出结果:Derived::fun2()
解析:fun1()内部的fun2()等价于this->fun2()this是基类指针,触发动态联编,调用派生类虚函数


多态的实现原理

面向对象编程的三个基本概念

  • 数据抽象 ↔ 用类进行数据抽象,封装
  • 继承 ↔ 派生类继承基类成员
  • 动态绑定 ↔ 多态,编译器在运行时决定调用基类/派生类函数

动态联编

一条函数调用语句在编译时无法确定调用哪个函数,运行到该语句时才确定调用哪个函数,这种机制叫动态联编

为什么需要动态联编?

1
2
3
4
5
class A { public: virtual void Get(); };
class B : public A { public: virtual void Get(); };
void MyFunction( A * pa ) {
pa->Get();
}

编译时无法确定pa指向A/B对象,只能运行时判断调用哪个Get()

多态的实现解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Base {
public:
void fun1() { cout << "Base::fun1()" <<endl; fun2(); }
virtual void fun2() { cout << "Base::fun2()" << endl; }
};
class Derived: public Base {
public:
virtual void fun1() { cout << "Derived::fun1()" << endl; fun2(); }
void fun2() { cout << "Derived::fun2()" << endl; }
};
int main() {
Derived d;
Base * pBase = & d;
pBase -> fun1();
return 0;
}
1
2
Base::fun1()
Derived::fun2()

补充说明:
调用次序:Base::fun1() -> Derived::fun2()
fun1()fun2()等价于this->fun2()fun2是虚函数+基类指针,触发动态联编;运行时this指向派生类对象,调用派生类函数。

稍加修改的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Base {
public:
virtual void fun1() { cout << "Base::fun1()" <<endl; fun2(); }
void fun2() { cout << "Base::fun2()" << endl; }
};
class Derived: public Base {
public:
virtual void fun1() { cout << "Derived::fun1()" << endl; fun2(); }
void fun2() { cout << "Derived::fun2()" << endl; }
};
int main() {
Derived d;
Base * pBase = & d;
pBase -> fun1();
return 0;
}

输出:Derived::fun1() 、Derived::fun2()
解析:fun1是虚函数→动态联编;派生类fun1中调用普通函数fun2,直接调用自身版本

虚函数表(多态的核心实现)

思考例子

1
2
3
4
5
6
7
8
9
10
11
12
13
class Base1 {
public: int i;
virtual void Print() { cout << "Base1:Print" ; }
};
class Derived : public Base1{
public: int n;
virtual void Print() { cout <<"Derived:Print"<< endl; }
};
int main() {
Derived d;
cout << sizeof( Base1 ) << ", "<< sizeof( Derived ) ;
return 0;
}

输出:8, 12
疑问:对象大小多出4字节?这与多态有何关联?

咦?

神奇的是对于8字节,会有pad8字节对齐

原理:多出的空间用于存储虚函数表指针

  1. 含虚函数的类/派生类,都有虚函数表,对象中存储虚函数表指针(4/8字节)
  2. 虚函数表存储类的所有虚函数地址
  3. 基类指针调用虚函数时,通过对象的虚表指针找到对应函数,实现动态绑定

调用过程Base* pBase = &d; pBase->Print();

  • pBase指向Derived对象,虚表指针指向Derived类虚表
  • 虚表中Print地址为Derived::Print
  • 程序调用派生类函数

虚函数的访问权限

访问权限检查是根据指针/引用的类型,而非运行时指向的对象类型。

1
2
3
4
5
6
7
8
9
10
11
12
class Base {
private: virtual void fun2() { cout << "Base::fun2()" << endl; }
};
class Derived: public Base {
public: virtual void fun2() { cout << "Derived::fun2()" << endl; }
};
int main() {
Derived d;
Base * pBase = & d;
pBase -> fun2(); // 编译出错
return 0;
}

编译出错:基类fun2是私有成员,语法检查直接报错

1
2
3
4
5
6
7
8
9
10
11
12
class Base {
public: virtual void fun2() { cout << "Base::fun2()" << endl; }
};
class Derived:public Base {
private: virtual void fun2() { cout << "Derived::fun2()" << endl; }
};
int main() {
Derived d;
Base*pBase=&d;
pBase->fun2(); // 编译通过,运行调用Derived::fun2()
return 0;
}

编译通过:基类函数公有→语法检查通过;运行动态绑定派生类私有函数

构造函数和析构函数中调用虚函数

⚠️规则构造/析构函数中调用虚函数为静态联编仅调用当前类/基类函数;普通成员函数中才是动态联编。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class myclass{
public:
virtual void hello(){ cout<<"hello from myclass"<<endl; };
virtual void bye(){ cout<<"bye from myclass"<<endl; };
};
class son : public myclass{
public:
void hello() { cout<<"hello from son"<<endl; };
son() { hello(); };
~son() { bye(); };
};
class grandson : public son{
public:
void hello() { cout<<"hello from grandson"<<endl; };
grandson() { cout<<"constructing grandson"<<endl; };
~grandson() { cout<<"destructing grandson"<<endl; };
};
int main(){
grandson gson;
son *pson;
pson=&gson;
pson->hello();
return 0;
}

输出

1
2
3
4
5
hello from son
constructing grandson
hello from grandson
destructing grandson
bye from myclass

原因

  1. 构造时:先调用基类构造,派生类未初始化,对象身份为基类,调用基类虚函数
  2. 普通调用:对象构造完成,动态绑定派生类函数
  3. 析构时:先析构派生类,对象身份回到基类,调用基类函数

测一测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class A {
public:
virtual void Func() { cout << "A::Func" << endl; }
};
class B:public A {
public:
virtual void Func() { cout << "B::Func" << endl; }
};
int main() {
A a;
A * pa = new B();
pa->Func();
//64位程序用long long
long * p1 = (long * ) & a;
long * p2 = (long * ) pa;
* p2 = * p1;
//把 A 的 vptr 复制过去,覆盖 B 的 vptr
pa->Func();
return 0;
}

输出结果:B::Func 、A::Func
解析:

  1. 第一次调用:指向B对象→调用B::Func
  2. 赋值操作:将A的虚表指针覆盖B的虚表指针
  3. 第二次调用:通过A的虚表→调用A::Func
1
2
3
long * p1 = (long *) &a;   // p1 指向 A 对象的首地址(把它当成 8 字节整数来看)
long * p2 = (long *) pa; // p2 指向 B 对象的首地址(也当成 8 字节整数)
* p2 = * p1; // 只复制了这 8 个字节!

和对象赋值完全不是一回事,把 A 对象的前 8 个字节(恰好是它的 vptr)复制到 B 对象的前 8 个字节位置,覆盖了 B 的 vptr。

B 对象的其他部分——如果它有数据成员的话——完全没被碰


虚析构函数

问题背景

⚠️⚠️通过基类指针删除派生类对象,仅调用基类析构函数,派生类资源未释放,造成内存泄漏。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class son{
public:
~son() { cout<<"bye from son"<<endl; };
};
class grandson : public son{
public:
~grandson(){ cout<<"bye from grandson"<<endl; };
};
int main(){
son *pson;
pson=new grandson;
delete pson;
return 0;
}

输出:bye from son
问题:未调用派生类析构函数,资源泄漏

解决办法

基类析构函数声明为virtual,派生类析构函数可省略virtual。
通过基类指针删除对象时,先调用派生类析构,再调用基类析构。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class son {
public:
virtual ~son() { cout<<"bye from son"<<endl; };
};
class grandson : public son {
public:
~grandson(){ cout<<"bye from grandson"<<endl; };
};
int main() {
son *pson;
pson= new grandson;
delete pson;
return 0;
}

输出:
bye from grandson
bye from son

通用规则:类定义了虚函数,析构函数必须定义为虚函数。

构造函数能否声明为virtual?

不能
⚠️原因:构造函数负责初始化虚函数表指针,而虚函数调用依赖该指针;构造函数执行前指针未初始化,矛盾。


纯虚函数和抽象类

纯虚函数

没有函数体的虚函数,声明格式:virtual 返回值 函数名(参数) = 0;

1
2
3
4
5
6
class A {
private: int a;
public:
virtual void Print( ) = 0 ; //纯虚函数
void fun() { cout << "fun"; }
};

抽象类

包含纯虚函数的类为抽象类,特性:

  1. 不能创建抽象类对象,只能作为基类派生
  2. 抽象类指针/引用可指向派生类对象
  3. 成员函数可调用纯虚函数;构造/析构函数不能调用
  4. 派生类必须实现所有纯虚函数,否则仍为抽象类

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class A {
public:
virtual void f() = 0; //纯虚函数
A( ){ }
void g( ) { f( ) ; } //等价this->f(),合法
//A(){ f(); } 错误:构造函数不能调用纯虚函数
};
class B : public A{
public:
void f(){ cout<<"B:f()"<<endl ; }
};
int main(){
B b;
b.g( ); //输出B:f()
return 0;
}

注意:构造函数调用纯虚函数,虚表无实现,程序崩溃

抽象类不可以生成对象, 都包含纯虚函数,抽象类的纯虚函数可以有函数体,派生类可以调用抽象类的带函数体的纯虚函数,需要实例化纯虚函数后才可以生成对象,但是派生类还是要自己实现纯虚函数


访问机制对比(隐藏 vs 虚函数)

隐藏

由指针/引用的类型决定调用:

  • 派生类指针→调用派生类同名函数
  • 基类指针→调用基类同名函数(即使指向派生类)

虚函数

由指针/引用指向的对象类型决定调用:

  • 指向基类→调用基类虚函数
  • 指向派生类→派生类虚函数

总结

  1. 虚函数和多态:基类定义接口,派生类实现;通过基类指针/引用实现动态调用
  2. 多态的作用:提升程序扩展性,新增功能无需修改原有执行代码
  3. 多态的实现:依赖虚函数表+虚表指针,运行时动态查找函数
  4. 访问权限:语法检查基于指针类型,基类虚函数建议设为public
  5. 纯虚函数与抽象类:纯虚函数=0;抽象类不能实例化,派生类必须实现所有纯虚函数
  6. 虚函数调用:普通函数→动态联编;构造/析构→静态联编
  7. 虚析构函数:(如果一个类有任何虚函数,或者意图作为多态基类被继承)含虚函数的类必须定义虚析构,避免内存泄漏;构造函数不能为虚函数

补充:函数指针与qsort库函数

函数指针-基本概念

函数名是函数的入口地址;指向函数的指针称为函数指针,可通过指针调用函数。

⚠️函数指针-定义形式

1
类型名(*指针变量名)(参数类型1, 参数类型2, ...);
  • 类型名:函数返回值类型
  • 括号内:函数参数类型

⚠️示例**:int (*pf)(int, char);

函数指针-使用方法

  1. 用函数名给函数指针赋值
  2. 指针名(实参)调用函数

示例

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
void PrintMin(int a, int b){
if( a < b ) printf("%d", a);
else printf("%d", b);
}
int main(){
int x = 4, y = 5;
void (* pf)(int, int);
pf = PrintMin;
pf(x, y); //输出4
return 0;
}

函数指针和qsort库函数

C快速排序函数,可排序任意类型数组:

1
2
void qsort(void *base, int nelem, unsigned int width,
int (* pfCompare)(const void *, const void *));

参数说明

  1. base:数组首地址
  2. nelem:元素个数
  3. width:单个元素字节数
  4. pfCompare:自定义比较函数指针

比较函数的要求

原型:int 函数名(const void * elem1, const void * elem2);
返回值规则:

  1. elem1排在前 → 返回负数
  2. 相等 → 返回0
  3. elem1排在后 → 返回正数

qsort使用示例

按数字个位数排序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <stdlib.h>
int MyCompare(const void * elem1, const void * elem2 ){
unsigned int * p1 = (unsigned int *) elem1;
unsigned int * p2 = (unsigned int *) elem2;
return (*p1 % 10) - (*p2 % 10 );
}
#define NUM 5
int main(){
unsigned int an[NUM] = { 8, 123, 11, 10, 4 };
qsort(an, NUM, sizeof(unsigned int), MyCompare);
for(int i = 0; i < NUM; i ++ )
printf("%d ", an[i]); //输出10 11 123 4 8
return 0;
}