关于C++中继承这个概念相比大家都很熟悉,那么子类究竟能从父类继承到哪些东西,哪些东西又是子类继承不到的呢?可能很多人都会
觉得父类所有的东西都会被子类继承,包括成员函数和成员变量,否则就违背了“父子关系
”这字面上的意思,其实不然,首先我们先看一小段简单的代码
,这段代码很容易理解
仔细看主函数里面的注释,这段代码几乎会让很多人第一次看到都很诧异
首先普及两点常识,大虾略过
1. 重载这个概念很重要,这是C++比C多出来的功能,我们知道C是不支持函数重载的,这也是限制C开发大型程序的原因之一。
重载发生在同一个类中(父类和子类不叫同一类,虽然可以称他们为同一类型)
函数名称相同,参数不同
函数重载忽略返回值和virtual关键字(如果仅仅是返回值不同或有无virtual关键字的区别,不算函数重载 )
下面是对函数重载为什么忽略返回值的解释:
void Function(void);
int Function (void);
上述两个函数,第一个没有返回值,第二个的返回值是int 类型。如果这样调用函数:
int x = Function ();
则可以判断出Function 是第二个函数。问题是在C++/C 程序中,我们可以忽略函数的返回值。在这种情况下,编译器和程序员都不知道哪个Function 函数被调用。
所以只能靠参数而不能靠返回值类型的不同来区分重载函数。编译器根据参数为每个重载函数产生不同的内部标识符
2. 隐藏这个概念
隐藏就是“内部作用域”的名称隐藏"外部作用域“的名称,例如局部变量会隐藏同名的全局变量,同样, 子类的名称会隐藏父类的名称。这篇文章主要解释的就是关于继承中存在的隐藏的问题
好了,有了上面的知识,我们就可以对上面的代码加以分析了。Base类中的fun1和fun2函数都被重载了,我们在主函数中调用fun1()函数是没有问题的,因为在Derived类中存在这个函数,这里我们调用的fun1()就是Derived类中的fun1()。再往后面看,我们调用fun1(8)函数会出现错误,在Base类中明明存在virtual void fun1(int)这个函数,我们又使用了public继承,为什么这里调用会出错呢?难道是没有继承到fun1(int)这个函数,是不是很奇怪?这是因为子类中存在fun1()函数,编译器在子类中找到fun1()这个符号,就不会再去父类里查找fun1(int),虽然父类里面有我们需要的函数。但是编译器不管,只要找到fun1()这个符号,它就再往下查找。这就导致了父类中所有与子类同名的函数都将被隐藏,而这肯定与我们绝大多数时候的意愿是相违背的。
C++这么做是有原因的,记住一句话,C++每一种行为的背后都有其深层的原理,搞清楚这背后的原理对我们理解C++很有帮助。
关于这种行为背后原理的权威解释,在《Effetive C++》中有这么一段话:
”这些行为背后的原理是为了防止你在程序库或应用框架内建立新的derived class时从疏远的的base classes继承重载函数“。
仔细分析下这句话C++这么做确实有道理的,如果程序规模比较大,类继承层次比较深时,如果C++没有这种隐藏特性,那么子类中的重载函数数量就会非常多,这样就增加了我们调用这些函数出错的可能性。但是很多情况下我们不需要编译器这样”弄巧成拙“,那么如何解决这个问题,也就是让编译器不要隐藏域子类同名的函数。显然C++是给出了解决办法的,我们可以使用using声明来解决这个问题。看下面的代码
通过在Derived类中引入using申明,我们将Base类中的所有fun1,fun2, fun3函数添加进了子类,这样子类对象在调用这些函数时就没有任何问题了。
所以当我们继承父类,而且父类存在重载函数,我们又在子类重新定义了这些重载函数的一部分,那么我们必须为那些原本会被隐藏的名称加上using申明,否则那些我们希望继承的名称会被隐藏掉而又不给我们任何提示。
现在让我们再深入一点,如果我们不希望继承所有父类中的成员函数(这里与今天的主题并不矛盾),这在public继承中是不可能的,也是没有意义的。在public继承中父类和子类关系式is-a。然后这在private继承中是有意义的, 这么说吧,假设我们只想继承Base类中带参数的fun2版本,即void fun2(int)这个函数。上面提到的using声明显然已经不足以解决这个问题,因为using声明会将父类中所有指定
名称的函数都在子类中可见,所以需要另外的解决办法,看代码:
在这段代码中,将Base类中的fun2(int)函数放在Derived类中的CallBaseFun2(int x)函数中实现调用,而Base中的无参数fun2版本void fun2()不能调用。好了,关于C++中继承和隐藏的类容就差不多了。
总结:
Derived类中的名称会隐藏Base类中同名的名称,在public继承中我们可以通过引入using声明,在private继承中我们可以通过一个转接函数来解决这个问题。
点击(此处)折叠或打开
- #include <iostream>
-
- using namespace std;
- class Base
- {
- public:
- virtual void fun1() = 0;
- virtual void fun1(int)
- {
-
- }
- virtual void fun2()
- {
-
- }
- void fun2(int)
- {
-
- }
-
- void fun3(float)
- {
-
- }
- };
-
- class Derived:public Base
- {
- public:
- virtual void fun1()
- {
-
- }
- void fun3()
- {
-
- }
- void fun4()
- {
-
- }
-
- };
-
-
- int main()
- {
- Derived a;
- a.fun1(); //correct Derived:fun1();
- //a.fun1(8); //incorrect
- a.fun2(); //correct Base:fun2()
- a.fun2(3); //correct Base:fun2(int);
- a.fun3(); //correct Derived:fun3()
- //a.fun3(3.0); //incorrect
- cout << "Hello world!" << endl;
- return 0;
- }
首先普及两点常识,大虾略过
1. 重载这个概念很重要,这是C++比C多出来的功能,我们知道C是不支持函数重载的,这也是限制C开发大型程序的原因之一。
重载发生在同一个类中(父类和子类不叫同一类,虽然可以称他们为同一类型)
函数名称相同,参数不同
函数重载忽略返回值和virtual关键字(如果仅仅是返回值不同或有无virtual关键字的区别,不算函数重载 )
下面是对函数重载为什么忽略返回值的解释:
void Function(void);
int Function (void);
上述两个函数,第一个没有返回值,第二个的返回值是int 类型。如果这样调用函数:
int x = Function ();
则可以判断出Function 是第二个函数。问题是在C++/C 程序中,我们可以忽略函数的返回值。在这种情况下,编译器和程序员都不知道哪个Function 函数被调用。
所以只能靠参数而不能靠返回值类型的不同来区分重载函数。编译器根据参数为每个重载函数产生不同的内部标识符
2. 隐藏这个概念
隐藏就是“内部作用域”的名称隐藏"外部作用域“的名称,例如局部变量会隐藏同名的全局变量,同样, 子类的名称会隐藏父类的名称。这篇文章主要解释的就是关于继承中存在的隐藏的问题
好了,有了上面的知识,我们就可以对上面的代码加以分析了。Base类中的fun1和fun2函数都被重载了,我们在主函数中调用fun1()函数是没有问题的,因为在Derived类中存在这个函数,这里我们调用的fun1()就是Derived类中的fun1()。再往后面看,我们调用fun1(8)函数会出现错误,在Base类中明明存在virtual void fun1(int)这个函数,我们又使用了public继承,为什么这里调用会出错呢?难道是没有继承到fun1(int)这个函数,是不是很奇怪?这是因为子类中存在fun1()函数,编译器在子类中找到fun1()这个符号,就不会再去父类里查找fun1(int),虽然父类里面有我们需要的函数。但是编译器不管,只要找到fun1()这个符号,它就再往下查找。这就导致了父类中所有与子类同名的函数都将被隐藏,而这肯定与我们绝大多数时候的意愿是相违背的。
C++这么做是有原因的,记住一句话,C++每一种行为的背后都有其深层的原理,搞清楚这背后的原理对我们理解C++很有帮助。
关于这种行为背后原理的权威解释,在《Effetive C++》中有这么一段话:
”这些行为背后的原理是为了防止你在程序库或应用框架内建立新的derived class时从疏远的的base classes继承重载函数“。
仔细分析下这句话C++这么做确实有道理的,如果程序规模比较大,类继承层次比较深时,如果C++没有这种隐藏特性,那么子类中的重载函数数量就会非常多,这样就增加了我们调用这些函数出错的可能性。但是很多情况下我们不需要编译器这样”弄巧成拙“,那么如何解决这个问题,也就是让编译器不要隐藏域子类同名的函数。显然C++是给出了解决办法的,我们可以使用using声明来解决这个问题。看下面的代码
点击(此处)折叠或打开
- #include <iostream>
-
- using namespace std;
- class Base
- {
- public:
- virtual void fun1() = 0;
- virtual void fun1(int)
- {
-
- }
- virtual void fun2()
- {
-
- }
- void fun2(int)
- {
-
- }
-
- void fun3(float)
- {
-
- }
- };
-
- class Derived:public Base
- {
- public:
- using Base::fun1;
- using Base::fun2;
- using Base::fun3;
- virtual void fun1()
- {
-
- }
- void fun3()
- {
-
- }
- void fun4()
- {
-
- }
-
- };
-
-
- int main()
- {
- Derived a;
- a.fun1(); //correct Derived:fun1();
- a.fun1(8);
- a.fun2(); //correct Base:fun2()
- a.fun2(3); //correct Base:fun2(int);
- a.fun3(); //correct Derived:fun3()
- a.fun3(3.0);
- cout << "Hello world!" << endl;
- return 0;
- }
所以当我们继承父类,而且父类存在重载函数,我们又在子类重新定义了这些重载函数的一部分,那么我们必须为那些原本会被隐藏的名称加上using申明,否则那些我们希望继承的名称会被隐藏掉而又不给我们任何提示。
现在让我们再深入一点,如果我们不希望继承所有父类中的成员函数(这里与今天的主题并不矛盾),这在public继承中是不可能的,也是没有意义的。在public继承中父类和子类关系式is-a。然后这在private继承中是有意义的, 这么说吧,假设我们只想继承Base类中带参数的fun2版本,即void fun2(int)这个函数。上面提到的using声明显然已经不足以解决这个问题,因为using声明会将父类中所有指定
名称的函数都在子类中可见,所以需要另外的解决办法,看代码:
点击(此处)折叠或打开
- #include <iostream>
-
- using namespace std;
- class Base
- {
- public:
- virtual void fun1() = 0;
- virtual void fun1(int)
- {
-
- }
- virtual void fun2()
- {
-
- }
- void fun2(int)
- {
-
- }
-
- void fun3(float)
- {
-
- }
- };
-
- class Derived:private Base
- {
- public:
- virtual void fun1()
- {
- cout<<"This function is implemented to avoid to be a abstract class";
- }
- void CallBaseFun2(int x)
- {
- Base::fun2(x);
- }
-
-
- };
-
- int main()
- {
- Derived a;
- CallBaseFun2(3);
- //a.fun2(); //incorrect
-
- cout << "Hello world!" << endl;
- return 0;
- }
总结:
Derived类中的名称会隐藏Base类中同名的名称,在public继承中我们可以通过引入using声明,在private继承中我们可以通过一个转接函数来解决这个问题。