关于隐藏、覆盖(重写)、重载的理解

定义区分

  • 在派生-对象中:优先考虑隐藏,此时派生类中的覆盖(重写)也是隐藏;没有隐藏的情况下,子类对象才能调用父类重载函数。[此时感觉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 = &dd;
	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;
}
相关推荐
福鸦1 小时前
详解c++:new和delete
开发语言·c++
createcrystal2 小时前
《算法笔记》例题解析 第3章入门模拟--3图形输出(9题)2021-03-03
c++·笔记·算法
tan77º3 小时前
【C++】异常
c++·算法
薛文旺4 小时前
c++可视化打印树
开发语言·c++
DogDaoDao4 小时前
Windows 环境下 vscode 配置 C/C++ 环境
c语言·c++·windows·vscode·gcc·mingw-w64
q4725994514 小时前
OpenGL 原生库6 坐标系统
c++
Once_day4 小时前
C++(2)进阶语法
c++
vczxh4 小时前
c++ templates常用函数
开发语言·c++
XXXJessie4 小时前
c++249多态
java·c++·servlet
jianglq4 小时前
深入剖析:C++类对象的内存布局与优化
c++