小谈C++的函数重载,成员函数覆盖与成员函数隐藏
函数重载:
函数重载是指在同一作用域内,可以有一组具有相同函数名,不同参数列表的函数,这组函数被称为重载函数。在一个类内,可以将功能相似的函数进行重载,一方面方便调用,另一方面使得类简洁。注意同一作用域不单单指类作用域内的成员函数,非成员的全局函数也可以进行重载。
其规则为:相同的函数名,不同的参数列表。不同的参数列表包括参数的类型不同,和参数的数量不同两种。注意不同的返回值类型无法实现函数重载。重载函数与virtual关键字无关。
下面例子是一个函数重载的例子,包括类成员函数的重载和非成员函数重载。
#include <iostream>
#include <string>
using namespace std;
class Overload
{
public:
Overload(){ adder = 0;}
int add(int a);
int add(int a, int b);
string add(string a,string b);
private:
int adder;
};
int Overload::add(int a)
{
adder += a;
return adder;
}
int Overload::add(int a, int b)
{
return a + b;
}
string Overload::add(string a, string b)
{
return a + b;
}
// 与void print()冲突,编译错误
//int print()
//{
// cout << "Intprint!" << endl;
//}
void print()
{
cout << "voidprint!" << endl;
}
void print(int para)
{
cout << "Theint para value is : " << para << endl;
}
void print(string para)
{
cout << "Thestring para value is : " << para << endl;
}
int main()
{
Overload overload;
print(overload.add(10));
print(overload.add(10,10));
print(overload.add("Hello ", "World!"));
print();
return 0;
}
运行结果:
The int paravalue is : 10
The int paravalue is : 20
The string paravalue is : Hello World!
void print!
上述代码中,add具有不同参数个数的重载函数,具有相同参数个数,不同参数类型的重载函数。注意:对于不同参数个数的重载函数,如果前面参数相同,而多出的参数不可以设置默认值,否则便也也是错误。如下例:
int add(int a);
int add(int a, int b = 0);
此时由于b有了默认值,对第二参数的调用则可以使用add(1)这样单个参数的形式进行调用,将产生歧义。
函数覆盖:
覆盖特指的子类函数覆盖或改写父类虚函数的情况。
1.不同的范围中(位于基类与派生类)
2.函数名字相同
3.参数相同
4.基类函数必须是virtual函数
这里实现的也就是C++中的动态绑定。将子类对象赋给父类指针或引用时,调用覆盖的函数,则调用子类的对应函数,实现动态绑定。而如果函数没有进行覆盖,则会调用父类的函数。如下实例进行说明:
#include <iostream>
using namespace std;
class Base
{
public:
Base():a(1), b(2){};
virtual void printa();
void print();
void printb();
protected:
int a;
int b;
};
void Base::printa()
{
cout << "a :" << a << endl;
}
void Base::printb()
{
cout << "Base->> ";
cout << "b :" << b << endl;
cout << "Baseprintb call print a ->>";
printa();
}
void Base::print()
{
cout << "Baseclass!" << endl;
}
class Derived : public Base
{
public:
Derived(){a = 11; b = 12;}
virtual void printa();
void print(string b);
void printb();
};
void Derived::printa()
{
cout << "a :" << a << endl;
}
void Derived::printb()
{
cout <<"Derived->> ";
cout << "b :" << b << endl;
cout <<"Derived printb call printa ->>";
}
void Derived::print(string b)
{
cout <<"Derived class!--" << b << endl;
}
int main()
{
Base * base = newDerived();
base->printa();
base->printb();
base->print();
//base->print("Hello world");
return 0;
}
运行结果:
a : 11
Base->> b: 12
Base printb callprint a ->>a : 11
Base class!
解析:由于在基类中printa为虚函数,在子类中进行了覆盖,可以实现动态绑定,因此调用子类自己的printa函数,输出a : 11。
对于printb,由于它没有声明为virtual,因此它不会实现动态绑定,因此调用的是基类的printb函数,又因为在对成员函数进行调用时,需要默认传入this指针作为参数,由于此时base指针指向的是Derived的对象,因此它传入的也是自己所指向的对象的this指针,因此在printb里面打印出了 Base-> b:12的结果。!!!注意:其实C++中对成员函数已经通过C++的名字修饰的处理,将他们变为了类似全局函数的函数,所不同的是如果在函数中使用到了成员,则会为函数添加this指针作为隐式参数!!!
下面这一句则充分证明了虽然调用的是Base的printb,但是传入的是Derived的this指针,在printb中调用printa方法,由于此时传入的this指针为Derived对象的this指针,仍然发生动态绑定,打印的为a : 11
最后一句无需解释,上述解析明白之后,这个自然明白。
总结一句:如果使用virtual 关键字说明函数,则对函数的调用完全是根据调用时使用的类的类型来决定。如果用到了成员,则会想函数中传递this指针,this指针由当前的实际对象来决定。
注:由于父类对象无法赋给子类指针或引用,这样的赋值时违反C++的规则的。因此不存在子类指针指向的父类对象,在调用函数时传递给函数的为父类对象的情况出现。其他两种情况:父类指针指向父类对象,子类指针指向子类对象都不存在上述的问题。此处不再额外阐述。
隐藏:
这里的” 隐藏”是指派生类的函数屏蔽了与其同名的基类函数。
C++中的隐藏规则:
1.如果派生类的函数与基类函数名字相同,但是参数不同,不论有无virtual关键字,基类的函数将被隐藏。(注意不要与重载混淆)
2.如果派生类的函数与基类函数同名,并且参数也相同,但是基类函数没有virtual关键字,此时基类的函数被隐藏。(注意不要与覆盖混淆)
此处使用网上他人写的例子,没有记错应该是林锐的高质量C/C++编程中的代码。
#include<iostream.h>
using namespace std;
class Base
{
public:
virtual void f(float x){cout << "Base::f(float) " << x << endl;}
void g(float x){ cout<< "Base::g(float) " << x << endl;}
void h(float x){ cout<< "Base::h(float) " << x << endl;}
};
class Derived : public Base
{
public:
virtual void f(float x){cout << "Derived::f(float) " << x << endl;}
void g(int x){ cout<< "Derived::g(int) " << x << endl;}
void h(float x){ cout<< "Derived::h(float) " << x << endl;}
};
int main(void)
{
Derived d;
Base *pb = &d;
Derived *pd = &d;
// 没有问题,动态绑定
pb->f(3.14f); // Derived::f(float) 3.14
pd->f(3.14f); // Derived::f(float) 3.14
// 相同名字,但是不同参数,为隐藏
pb->g(3.14f); // Base::g(float) 3.14
pd->g(3.14f); // Derived::g(int) 3 (surprise!)
// 相同名字与参数,但是没有virtual,为隐藏
pb->h(3.14f); // Base::h(float) 3.14 (surprise!)
pd->h(3.14f); // Derived::h(float) 3.14
return 0;
}
运行结果:
Derived::f(float)3.14
Derived::f(float)3.14
Base::g(float)3.14
Derived::g(int)3
Base::h(float)3.14
Derived::h(float)3.14
解析:
前两行没有异议,与上一节的覆盖相同。第三行和第四行,则是隐藏规则1中所描述的,派生类中出现了与父类中同名但是不同参数的函数,此时会将父类的同名函数隐藏掉。最后两行则是对没有virtual修饰的同名同参函数的说明。
注:假设注释掉子类中的g函数,同样的代码,第三和第四行的输出将相同。但是调用函数时传入的this指针不是同一个this指针。
By andy