定义区分
- 在派生-对象中:优先考虑隐藏,此时派生类中的覆盖(重写)也是隐藏;没有隐藏的情况下,子类对象才能调用父类重载函数。[此时感觉virtual没用,]
- 在派生-指针或者引用 中:只用覆盖(重写)和重载;
注:C++ Primmer P550解释为:D1的fnc并没有覆盖(重写)Base的虚函数fnc...实际上,D1...此时拥有了两个名为fnc的函数:一个是D1从Base继承而来的(隐藏的)虚函数fnc;另一个是D1自己定义的接受的接受int参数的非虚函数fnc。
其他有利于理解的说法:如果派生类定义了一个与基类成员函数同名的函数(无论参数列表是否相同 ),那么派生类中的这个函数将隐藏基类中的所有同名函数(隐藏仅仅指类的对象,对于指向派生类的基类指针仍然可以调用其他虚函数)
下面是我写的两个函数示例(后续优化)
/*
* 知识点:隐藏、覆盖(重写)、重载
* One:对于隐藏、覆盖(重写)
* 在派生-对象中:优先考虑隐藏,此时派生类中的覆盖(重写)也是隐藏;没有隐藏的情况下,子类对象才能调用父类重载函数。[此时感觉virtual没用,]
* 在派生-指针或者引用中:只用覆盖(重写)和重载;
*
*
其他有利于理解的说法
* 如果派生类定义了一个与基类成员函数同名的函数(无论参数列表是否相同),
* 那么派生类中的这个函数将隐藏基类中的所有同名函数(隐藏仅仅指类的对象,
* 对于指向派生类的基类指针仍然可以调用其他虚函数)
*/
#include <iostream>
using namespace std;
class Base
{
public:
Base(int a) : m_v(a) {}
virtual void setM() { m_v = 0; }
virtual void setM(int a) { m_v = a; }
virtual int getM() { return m_v; }
protected:
int m_v;
};
class Drive : public Base
{
public:
Drive() : Base(42) {}
void setM() override { m_v = 1000; } // 使用 override 关键字明确这是一个重写的方法
};
class DDrive : public Drive
{
public:
DDrive() : Drive() {}
};
int main()
{
Base b(6);
cout << "Base: " << b.getM() << endl; // 输出 6
b.setM();
cout << "Base setM() default(0): " << b.getM() << endl; // 输出 0,因为调用了 Base 的 setM()
b.setM(9);
cout << "Base setM(9): " << b.getM() << endl; // 输出 9
cout << endl;
Drive d;
cout << "Drive Base default(): " << d.getM() << endl; // 输出 42,因为 Drive 的构造函数调用了 Base(42)
d.setM();
cout << "Drive setM() default(0): : " << d.getM() << endl; // 输出 1000,因为调用了 Drive 的 setM()
//d.setM(1001); // Error:函数调用参数过多;派生类对象对父类进行隐藏
//cout << "Drive (1001) : " << d.getM() << endl; // 输出 1000,因为调用了 Drive 的 setM()
// 以下部分展示了多态性
Base* pb = &b;
cout << "Base: " << pb->getM() << endl; // 输出 9
pb->setM();
cout << "Base: " << pb->getM() << endl; // 输出 0,因为 pb 指向 Base 对象,调用了 Base 的 setM()
pb->setM(9);
cout << "Base: " << pb->getM() << endl; // 输出 9
Base* pd = &d;
cout << "Drive: " << pd->getM() << endl; // 输出 1000
pd->setM();
cout << "Drive: " << pd->getM() << endl; // 输出 1000,因为 pd 指向 Drive 对象,调用了 Drive 的 setM()
pd->setM(1001);
cout << "Drive: " << pd->getM() << endl; // 输出 1001,因为调用了 Drive 的 setM(int)
// 延申:子类的不恰当隐藏会影响到孙类"!!!对象!!!",但并不影响孙类指针
DDrive dd;
cout << "Drive Base default(): " << dd.getM() << endl;
dd.setM();
cout << "Drive setM() default(0): : " << dd.getM() << endl;
//dd.setM(1001); // Error:函数调用参数过多;子类的不恰当隐藏会影响到孙类!!!对象!!!
cout << "Drive (1001) : " << dd.getM() << endl;
Base* pdd = ⅆ
cout << "Drive: " << pdd->getM() << endl;
pdd->setM();
cout << "Drive: " << pdd->getM() << endl;
pdd->setM(1001);
cout << "Drive: " << pdd->getM() << endl;
return 0;
}
还有个更长的示例
#include <iostream>
using namespace std;
/*
*一、
*在C++中,重写(Override)和隐藏(Hiding)是两个不同的概念,它们在某些情况下可能会产生冲突或混淆,尤其是在涉及继承时。
但是,这两个概念本身并不直接冲突,而是需要开发者明确理解和区分它们以避免潜在的问题。
- 重写(Override)
重写发生在子类中,当子类提供了一个与基类中的虚函数具有相同签名(即相同的函数名、返回类型、参数列表)的成员函数时。
这允许子类改变从基类继承的虚函数的行为。重写是动态绑定的一部分,因此当通过基类指针或引用调用重写的虚函数时,将调用子类中的版本(如果指针或引用实际上指向子类对象)。
- 隐藏(Hiding)
隐藏也发生在子类中,但它通常与名称冲突相关。当子类提供了一个与基类中的非虚函数具有相同名称的成员函数时,基类的该函数在子类中将被隐藏。
这意味着在子类的**对象**直接调用该函数时,将调用子类中的版本,而不是基类中的版本。
但是,如果通过基类指针或引用调用该函数(即使该指针或引用实际上指向子类对象),仍然会调用基类中的版本(除非基类中的函数也是虚函数)。
*冲突和混淆
- 重写和隐藏之间的主要混淆在于它们都与函数名和继承有关,但行为却截然不同。如果开发者不清楚何时发生重写、何时发生隐藏,可能会导致意外的行为。
- 此外,如果基类中的函数被设计为虚函数(以支持多态),但子类中的同名函数没有被声明为override(在C++11及更高版本中支持),并且基类中的函数签名在子类中发生了更改(例如参数数量或类型不同),
那么子类中的函数实际上将隐藏基类中的虚函数,而不是重写它。
这可能导致代码中的错误,因为开发者可能期望实现多态行为,但实际上却得到了隐藏行为。
为了避免这种混淆,建议:
- 在子类中重写基类的虚函数时,使用override关键字(如果编译器支持)。这将使编译器在基类中没有匹配的虚函数时发出错误。
- 尽量避免在子类中隐藏基类中的非虚函数,除非这是有意为之。如果必须这样做,请确保在文档和注释中清楚地说明这一点。
- 理解多态、重写和隐藏的概念,并始终注意在涉及继承时的函数签名和访问级别。
*二、如果你遇到d2.doSomething(3.14)不能调用Base类的doSomething(double a)方法的情况,可能有几个原因:
①隐藏基类方法:如果在Derived2或它的直接基类Derived中有任何重载版本的doSomething方法,并且没有使用using声明来引入基类的方法,基类的方法可能会被隐藏。
这意味着,即使基类中存在doSomething(double a),如果派生类中定义了其他版本的doSomething(如doSomething(int)或doSomething(char)),
但没有显式地引用基类的方法,基类版本的方法在派生类对象上可能无法直接访问。
②名称隐藏:在C++中,如果派生类中定义了与基类相同名称的成员函数,即使参数列表不同,基类中的同名函数也会被隐藏。
这是C++的名称隐藏规则,不同于重载。
③访问控制:如果Base类的doSomething方法是protected或private,则不能在Derived2类的对象上直接调用它(尽管这种情况不太可能,因为你提到了virtual,这通常意味着方法应该是public的)。
另一方面,dp2->doSomething(3.14);可以工作,可能是因为dp2是一个指向Derived2的指针,但是被当做Base类的指针来使用。
当你通过基类指针调用一个virtual函数时,C++的多态性确保调用的是指针实际指向的对象的最具体的实现(即动态绑定)。
如果Derived2没有重载doSomething(double a),则调用会回退到Base类的实现。
如果d2.doSomething(3.14)不工作,而dp2->doSomething(3.14);工作,最可能的原因是名称隐藏。
在派生类中定义新的doSomething函数时,如果没有正确地使用using声明来引入基类的函数,基类的同名函数会被隐藏
*/
class Base {
public:
virtual void doSomething(int a) {
// 基类的doSomething(int)方法
std::cout << "Base::doSomething(int) called with " << a << std::endl;
}
virtual void doSomething(char a) {
// 基类的doSomething(double)重载方法
std::cout << "Base::doSomething(char) called with " << a << std::endl;
}
virtual void doSomething(int a, int b, int c) {
// 基类的doSomething(double)重载方法
std::cout << "Base::doSomething(int a,int b, int c) called with a:" << a << ", b:" << b << ", c:" << c << std::endl;
}
void doSomething(int a, int b, int c, int d) {
// 基类的doSomething(double)重载方法
std::cout << "Base::doSomething(int a, int b, int c, int d) called with a:" << a << ", b:" << b << ", c:" << c << ", d:" << d << std::endl;
}
void coutName() {
// 基类的coutName重载方法
std::cout << "Base::coutName" << std::endl;
}
};
class Derived1 : public Base {
public:
//using Base::doSomething;
void doSomething(int a) {
// 派生类重写了基类的doSomething(int)方法
std::cout << "Derived1::doSomething(int) called with " << a << std::endl;
}
//using Base::doSomething;
void doSomething(char c) {
// 派生类重写了基类的doSomething(char c)方法
std::cout << "Derived1::doSomething(char) called with " << c << std::endl;
}
};
class Derived2 : public Base {
public:
//using Base::doSomething;
void doSomething(int a) {
// 派生类重写了基类的doSomething(int)方法
std::cout << "Derived2::doSomething(int) called with " << a << std::endl;
}
virtual void doSomething(int a, int b, int c, int d) {
// 派生类重写了基类的doSomething(int a, int b, int c, int d)方法
std::cout << "Derived2::doSomething(double) called with a:" << a << ", b:" << b << ", c:" << c << ", d:" << d << std::endl;
}
};
class Derived3 : public Base {
public:
//using Base::doSomething;
void doSomething(int a) {
// 派生类重写了基类的doSomething(int)方法
std::cout << "Derived3::doSomething(int) called with " << a << std::endl;
}
//using Base::doSomething;
void doSomething(int x, int y) {
// 派生类重载了doSomething(int x, int y)方法
std::cout << "Derived3::doSomething(int x, int y) called with x:" << x << ",y:" << y << std::endl;
}
};
class Derived11 : public Derived1 {
public:
//using Base::doSomething;
void doSomething(int a) {
// 派生类重写了基类的doSomething(int)方法
std::cout << "Derived11::doSomething(int) called with " << a << std::endl;
}
};
class Derived21 : public Derived2 {
public:
//using Base::doSomething;
void doSomething(int a) {
// 派生类重写了基类的doSomething(int)方法
std::cout << "Derived21::doSomething(int) called with " << a << std::endl;
}
};
class Derived31 : public Derived3 {
public:
//using Base::doSomething;
void doSomething(int a) {
// 派生类重写了基类的doSomething(int)方法
std::cout << "Derived31::doSomething(int) called with " << a << std::endl;
}
};
int main() {
cout << "************************************实例化对象开始表演******************************************************" << endl;
Base b1;
b1.doSomething(42);
//b1.doSomething(3.14); // !!!编译报错提示"有多个重载函数 实例与参数列表相同,26和31行",因为3.14不知道转换为int,还是char
b1.doSomething('c');
b1.doSomething(1, 2, 3);
b1.doSomething(1, 2, 3, 4);
b1.coutName();
cout << endl << endl;
Derived1 d1;
d1.doSomething(42); // 调用Derived1的doSomething(int)
//d1.doSomething(3.14); // !!!编译报错提示"有多个重载函数 实例与参数列表相同,56和62行",因为3.14不知道转换为int,还是char
d1.doSomething('c'); // !!!'c'转化内int
//d1.doSomething(1, 2, 3); // !!!报错提示"没有与参数列表匹配的 重载函数Derived1::doSomething, 参数类型为(int, int, int)"
//d1.doSomething(1, 2, 3, 4); // !!!报错提示"没有与参数列表匹配的 重载函数Derived1::doSomething, 参数类型为(int, int, int, int)"
d1.coutName(); // 调用基类的doSomething(double),因为派生类没有重写它
cout << endl << endl;
Derived2 d2;
d2.doSomething(42); // 调用Derived2的doSomething(int)
d2.doSomething(3.14); // 3.14转换为int,调用相同
d2.doSomething('c'); // 'c'转化内int
//d2.doSomething(1, 2, 3);//!!!报错提示"没有与参数列表匹配的 重载函数Derived2::doSomething, 参数类型为(int, int, int)"
d2.doSomething(1, 2, 3, 4); //!!!这是Derived2自身的函数,与Base没有关系
d2.coutName(); // 调用基类的doSomething(double),因为派生类没有重写它
// !!!子类Derived3::doSomething(int x, int y),是否对父类方法进行隐藏。也就是说d3是否拥有doSomething(double a)???
cout << endl << endl;
Derived3 d3;
d3.doSomething(42); // 调用Derived2的doSomething(int)
d3.doSomething(3.14); // 3.14转换为int,调用相同
d3.doSomething('c'); // 'c'转化内int
//d3.doSomething(1, 2, 3); // !!!报错提示"没有与参数列表匹配的 重载函数Derived3::doSomething, 参数类型为(int, int, int)"
//d3.doSomething(1, 2, 3, 4); // !!!报错提示"没有与参数列表匹配的 重载函数Derived3::doSomething, 参数类型为(int, int, int, int)"
d3.doSomething(1, 2); //!!!这是Derived3自身的函数,与Base没有关系
d3.coutName(); // 调用基类的doSomething(double),因为派生类没有重写它
cout << endl << endl;
cout << "******************创建Derived1、Derived2、Derived3单独的指针(感觉类实例化对象用法一致)*********************************************" << endl;
Derived1* opd1 = new Derived1;
opd1->doSomething(42); // 调用Derived1::doSomething(int)
opd1->doSomething('c'); // 调用Derived1::doSomething(char)
//opd1->doSomething(1, 2, 3); // !!!报错提示"没有与参数列表匹配的 重载函数Derived1::doSomething, 参数类型为(int, int, int)"
//opd1->doSomething(1, 2, 3, 4);// !!!报错提示"没有与参数列表匹配的 重载函数Derived1::doSomething, 参数类型为(int, int, int, int)"
opd1->coutName();
cout << endl << endl;
Derived2* opd2 = new Derived2;
opd2->doSomething(42); // 调用Derived2的doSomething(int)
opd2->doSomething('c'); // 调用Base::doSomething(char)
//opd2->doSomething(1, 2, 3);//!!!报错提示"没有与参数列表匹配的 重载函数Derived2::doSomething, 参数类型为(int, int, int)"
opd2->doSomething(1, 2, 3, 4); // !!!这是Derived2自身的函数,与Base没有关系
opd2->coutName();
cout << endl << endl;
Derived3* opd3 = new Derived3;
opd3->doSomething(42); // 调用Derived2的doSomething(int)
opd3->doSomething('c'); // 'c'转化内int
//opd3->doSomething(1, 2, 3);// !!!报错提示"没有与参数列表匹配的 重载函数Derived3::doSomething, 参数类型为(int, int, int)"
//opd3->doSomething(1, 2, 3, 4); // !!!报错提示"没有与参数列表匹配的 重载函数Derived3::doSomething, 参数类型为(int, int, int, int)"
opd3->doSomething(1, 2); // !!!报错提示"没有与参数列表匹配的 重载函数Base::doSomething, 参数类型为(int, int)"
opd3->coutName();
cout << endl << endl;
cout << "******************创建基类指针分别指向Derived1、Derived2、Derived3(virtual在派生类中多态应用)**************************************" << endl;
Base* pd1 = new Derived1;
pd1->doSomething(42); // 调用Derived1::doSomething(int)
pd1->doSomething('c'); // 调用Derived1::doSomething(char)
pd1->doSomething(1, 2, 3);// 调用Base::doSomething(int, int, int)
pd1->doSomething(1, 2, 3, 4);// 调用Base::doSomething(int, int, int, int)
pd1->coutName();
cout << endl << endl;
Base* pd2 = new Derived2;
pd2->doSomething(42); // 调用Derived2的doSomething(int)
pd2->doSomething('c'); // 调用Base::doSomething(char)
pd2->doSomething(1, 2, 3);// 调用Base::doSomething(int, int, int)
pd2->doSomething(1, 2, 3, 4); // !!! 这里调用基类Base::doSomething(int, int, int, int),因为不是虚函数,子类Derived2没有对其重写
pd2->coutName();
cout << endl << endl;
Base* pd3 = new Derived3;
pd3->doSomething(42); // 调用Derived2的doSomething(int)
pd3->doSomething('c'); // 'c'转化内int
pd3->doSomething(1, 2, 3);// 调用Base::doSomething(int, int, int)
pd3->doSomething(1, 2, 3, 4);// 调用Base::doSomething(int, int, int, int)
//pd3->doSomething(1, 2); // !!!报错提示"没有与参数列表匹配的 重载函数Base::doSomething, 参数类型为(int, int)"
pd3->coutName();
cout << endl << endl;
cout << "******************创建Derived11、Derived21、Derived31单独的指针(感觉类实例化对象用法一致)*********************************************" << endl;
Derived11* opd11 = new Derived11;
opd11->doSomething(42); // 调用Derived11::doSomething(int)
opd11->doSomething('c'); // 'c'转化内int
//opd11->doSomething(1, 2, 3); // !!!报错提示"参数太多",我感觉跟原来哪个意思一样("没有与参数列表匹配的 重载函数Derived11::doSomething, 参数类型为(int, int, int)")
//opd11->doSomething(1, 2, 3, 4);// !!!报错提示"参数太多"
opd11->coutName();
cout << endl << endl;
Derived21* opd21 = new Derived21;
opd21->doSomething(42); // 调用Derived21的doSomething(int)
opd21->doSomething('c'); // 'c'转化内int
//opd21->doSomething(1, 2, 3);// !!!报错提示"参数太多"
//opd21->doSomething(1, 2, 3, 4); // !!!报错提示"参数太多"
opd21->coutName();
cout << endl << endl;
Derived31* opd31 = new Derived31;
opd31->doSomething(42); // 调用Derived2的doSomething(int)
opd31->doSomething('c'); // 'c'转化内int
//opd31->doSomething(1, 2, 3);;// !!!报错提示"参数太多"
//opd31->doSomething(1, 2, 3, 4);;// !!!报错提示"参数太多"
//opd31->doSomething(1, 2); ;// !!!报错提示"参数太多"
opd31->coutName();
cout << endl << endl;
cout << "******************创建基类指针分别指向Derived11、Derived21、Derived31(virtual在派生类中多态应用)**************************************" << endl;
Base *pd11 = new Derived11;
pd11->doSomething(42); // 调用Derived1::doSomething(int)
pd11->doSomething('c'); // 调用Derived1::doSomething(char)
pd11->doSomething(1, 2, 3);// 调用Base::doSomething(int, int, int)
pd11->doSomething(1, 2, 3, 4);// 调用Base::doSomething(int, int, int, int)
pd11->coutName();
cout << endl << endl;
Base *pd21 = new Derived21;
pd21->doSomething(42); // 调用Derived2的doSomething(int)
pd21->doSomething('c'); // 调用Base::doSomething(char)
pd21->doSomething(1, 2, 3);// 调用Base::doSomething(int, int, int)
pd21->doSomething(1, 2, 3, 4); // !!! 这里调用基类Base::doSomething(int, int, int, int),因为不是虚函数,子类Derived2没有对其重写
pd21->coutName();
cout << endl << endl;
Base* pd31 = new Derived31;
pd31->doSomething(42); // 调用Derived2的doSomething(int)
pd31->doSomething('c'); // 'c'转化内int
pd31->doSomething(1, 2, 3);// !!!报错提示"参数太多"
pd31->doSomething(1, 2, 3, 4);// !!!报错提示"参数太多"
//pd31->doSomething(1, 2); // !!!报错提示"参数太多"
pd31->coutName();
cout << endl << endl;
return 0;
}