C++语言的一种设计思想就是,使一些功能可以重复使用,从而使操作更加便捷。
而重载(overloading)和重新定义(redefine)都是帮助C++实现这样的效果的功能。
1. 重载(overloading)
无论是函数的重载或者是操作符的重载,都可以极大简化后续代码的编写。以下举两个简单例子。
(1)函数重载
//函数重载,重载打印数字和字符串功能
void print(int a)
{
cout << a << endl;
}
void print(char * a)
{
cout << a << endl;
}
用户在使用该程序时无需关心变量是整形数还是字符串,都可以直接打印出来,使用起来非常方便。
(2)操作符重载
//操作符重载,重载操作符+
class coordinates
{
private:
double x;
double y;
public:
coordinates operator+(const coordinates & b) const
{
coordinates result;
result.x = x + b.x;
result.y = y + b.y;
return result;
}
};
通过重载“+”运算符,用户可以方便地对两个coordinates对象进行加法运算,而无需对每个对象内的成员依次进行相加。
2. 重新定义(redefine)
重新定义是类的继承(inheritance)功能中的一个重要内容。
首先简单说一下类的继承概念。
(1)类的继承
当你对一个类的功能有扩展需求时,或者当你无法修改一个类的内部结构却想增加它的功能时,就需要类的继承概念。
举一个很简单易懂的例子。定义一个名字为“工人”的类,工人可以有“身高”、“体重”、“年龄”等基本属性。但如果希望创建一个名为“建筑工人”类,该如何做呢?因为“建筑工人”也是一种工人,所以需要把“建筑工人”类的基本属性都赋予它,还需要追加一些仅有建筑工人才有的属性或功能,比如“搬砖”、“拌水泥”等。如果每出现一个新的工人种类就从头开始重新定义其所有功能和属性,那么工作量将非常大,而且低效率,不符合C++语言所强调的重复使用。因此出现了“继承”(inherit)这个概念。
使用继承功能可以直接将一个基类(base class)的成员和成员函数都复制给由这个基类派生(derive)出来的“派生类”(derived class)。再在此基础上为派生类添加一些它独自的成员或成员函数即可完成其定义。一个简单的例子如下。
//类的继承举例
//基类(base class)
class worker
{
private:
double height;
double weight;
int age;
public:
};
//派生类(derived class)
class builder
: public worker
{
public:
void pick_brick();
void mix_cement();
};
(2)虚函数(virtual function)与重新定义(redefine)
先不提“虚函数”和“重新定义”这两个很玄的名词,先从C++的基本编程思想开始。
C++希望尽可能地重复使用功能从而提高效率或者简化一些操作。
那么,可以继续上面的例子进行设想。
比如我在“建筑工人”(builder)之外增加了另一种工人“纺织工人”(cotton spinner),并将它作为“worker”基类的另一个派生类。现在如果我们想对他们进行工作的调度,比如希望要求建筑工人去工作,则需要告诉他“搬砖,然后拌水泥”,对纺织工人则需要告诉他“取棉花,然后放入机器”。然而,不管是要求哪种工人去做什么工作,“要求工人去工作”这件事的本质是没有区别的,可否只使用一个“go_to_work()”函数放在基类当中,就能让两种工人去做各自的工作呢?其实这个就可以通过虚函数和重新定义来实现。
“虚函数”,英文是virtual function,但我认为这个名称有些误导,“可被不同派生类多重定义的函数”似乎更符合这个名词的内涵。而“重新定义”,则表示在不同的派生类中对同一个名称的函数进行特定的定义。下面举一个简单例子如下来说明虚函数和重新定义的使用方法。
//虚函数和重新定义的举例
//基类,工人
class worker
{
private:
double height;
double weight;
int age;
public:
virtual void go_to_work()
{
cout << "Go to work.\n";
}
};
//派生类1,建筑工人
class builder
: public worker
{
public:
void go_to_work()
{
cout << "Go to pick bricks and mix cement.\n";
}
};
//派生类2,纺织工人
class cotton_spinner
: public worker
{
public:
void go_to_work()
{
cout << "Go to take cotton and put them into machine.\n";
}
};
在以上设定条件下:如果对一个建筑工人对象调用“go_to_work()”,则会出现“搬砖拌水泥”的指令;如果对一个纺织工人调用同样的方法,则会出现“取棉花放入机器”的指令。在基类中定义的同一个函数(或称为方法),在不同的派生类中可以有不同的定义(解释),调用方法相同,但结果根据具体派生类的区别而区别,这就是虚函数和重新定义的效果。
在基类中定义的虚函数仅在以基类创建的对象中生效,或者某些没有专门重新定义该虚函数的派生类中生效。
3. 重载与重新定义的相似之处和区别
重载和重新定义都使得“调用同一个函数名可以实现不同的功能”这一想法成为可能,这是二者的相似之处。
但除了“可以重复使用同一函数名”这一中心思想以外,二者的区别是相当大的。
重载函数和重载运算符,要求函数的参数列表必须不同。只有这样,才能使编译器根据被调用函数的参数列表类型来寻找所要调用的函数究竟是哪一种重载。
虚函数的重新定义,不在静态联编(static binding)过程中发生作用。编译器会在程序运行的过程中通过虚函数表(virtual function table)进行动态联编(dynamic binding),寻找对象所对应的具体派生类所使用的特定定义。
重载和重新定义,一个是使用函数的参数列表来确定重载方案,另一个是通过对象自身的派生类类别在该派生类的虚函数表中寻找其特定的定义。
4. 重载与重新定义的混合使用效果
如果在基类中使用了函数重载技术,又希望在派生类中重新定义这些被重载的函数,该怎么做呢?
答案是,将想在派生类中使用的每个函数重载都在派生类中进行重新定义,才能保证基类中的这些函数重载都可以被派生类使用。如果觉得这句话非常拗口,那么记住一条简单的规则:派生类中的重新定义,无论其重新定义的函数参数列表是什么样,都会隐藏其基类中所有同名的函数重载”。
派生类中的重新定义隐藏基类重载的简单例子如下。
//派生类重新定义隐藏基类函数重载的例子
//基类
class base
{
public:
virtual void print(int a) //对打印整形数的重载
{
cout << a << endl;
}
virtual void print() //对打印nothing的重载
{
cout << "nothing" << endl;
}
};
//派生类
class derived
: public base
{
public:
void print(char * a) //对打印字符串的重载
{
cout << a << endl;
}
};
//使用一个类型为derived类型的对象m
char * c = "abc";
int k = 3;
m.print(c); //该语句合法,因为派生类的重新定义可以打印字符串
m.print(); //该语句不合法
m.print(k); //该语句不合法
//因为派生类的重新定义隐藏了基类的
//打印数字和打印nothing的函数重载
为了在派生类中正常使用基类中所有的函数重载,必须在派生类的重新定义中,对基类的所有函数重载进行重新定义。具体内容如下。
//派生类重新定义隐藏基类函数重载的例子
//基类
class base
{
public:
virtual void print(int a) //对打印整形数的重载
{
cout << a << endl;
}
virtual void print() //对打印nothing的重载
{
cout << "nothing" << endl;
}
};
//派生类
class derived
: public base
{
public:
void print(char * a) //对打印字符串的重载
{
cout << a << endl;
}
void print(int a) //对打印整形数的重载
{
cout << "Derived class redefine.\n"; //重新定义了基类重载函数
cout << a << endl;
}
void print() //对打印nothing的重载
{
cout << "Derived class redefine.\n"; //重新定义了基类重载函数
cout << "nothing" << endl;
}
};
//使用一个类型为derived类型的对象m
char * c = "abc";
int k = 3;
m.print(c); //该语句合法,因为派生类的重新定义可以打印字符串
m.print(); //该语句合法,因为派生类有对应的重载类型的重新定义
m.print(k); //该语句合法,因为派生类有对应的重载类型的重新定义
以上就是我目前在学习C++语言过程中对重载和重新定义的一些简单总结。因为也是初学者,很多细节可能没有顾及到,如果有写的不全面或者有错误的地方,请留下宝贵批评。