• Index

继承中使用重名的成员

Reads: 1

派生类中使用重名的成员分两种情况讨论:第一种是在类内成员之间使用的情况;第二种是用派生类声明出对象后使用重名成员的情况。

基类和派生类重名的成员数据类型

基础示例 1

在讲解基类和派生类重名的成员数据类型之前,先看一下成员数据类型的使用。

#include <iostream> // std::cout std::endl

class base
{
public:
    using ullong = unsigned long long;
};

int main(void)
{
    base::ullong c = 100;
    std::cout << c << std::endl;
    return 0;
}

输出结果:

100

基础讲解 1

由于类的成员类型属于整个类的,因此不需要在创建一个对象之后才使用它。

基础示例 2

#include <type_traits> // std::is_same
#include <iostream> // std::cout std::endl std::boolalpha

class base
{
public:
    using ullong = unsigned long long;
};

class derived : public base
{
public:
    using ullong = unsigned long;
};

int main(void)
{
    std::cout << std::boolalpha;
    std::cout << "是否等于unsigned long long:" << std::is_same<base::ullong, unsigned long long>::value << std::endl;
    std::cout << "是否等于unsigned long:" << std::is_same<derived::ullong, unsigned long>::value << std::endl;
    return 0;
}

输出结果:

是否等于unsigned long long:true
是否等于unsigned long:true

基础讲解 2

基类将unsigned long long命名为ullong,在派生类中同样使用ullong,不过它是unsigned long的别名。从结果可以看出派生类的ullong就是unsigned long。换句话说,派生类的重名成员类型会覆盖掉基类的成员类型。

基类和派生类重名的成员变量

基础示例 1

首先看一下在类成员函数内使用重名成员变量的例子:

#include <iostream> // std::cout std::endl

class base
{
public:
    base(void);
protected:
    int m_value;
};

class derived : public base
{
public:
    derived(void);
public:
    void show_me(void) const noexcept;
    void show_base(void) const noexcept;
protected:
    int m_value;
};

int main(void)
{
    derived obj;
    obj.show_me();
    obj.show_base();
    return 0;
}

base::base(void)
    : m_value(2333333)
{
}

derived::derived(void)
    : m_value(666666)
{
}

void derived::show_me(void) const noexcept
{
    std::cout << "派生类的成员变量:" << m_value << std::endl;
}

void derived::show_base(void) const noexcept
{
    std::cout << "基类的成员变量:"  << base::m_value << std::endl;
}

输出结果:

派生类的成员变量:666666
基类的成员变量:2333333

基础讲解 1

代码中的派生类derived和基类base都有成员变量m_value,然后基类的m_value初始化为2333333,而派生类的m_value初始化为666666

从输出结果可以看出,在派生类内直接使用m_value,调用的是派生类的m_value,当需要使用基类的m_value时,需要写成base::m_value,这样派生类就给编译器指明了:我使用的是base继承下来的m_value而不是我自身m_value

基础示例 2

#include <iostream> // std::cout std::endl

class base
{
public:
    int m_value = 2333333;
};

class derived : public base
{
public:
    int m_value = 666666;
};

int main(void)
{
    derived obj;
    std::cout << obj.m_value << std::endl;
    std::cout << obj.base::m_value << std::endl;
    return 0;
}

输出结果:

666666

基础讲解 2

代码中的派生类derived和基类base都有成员变量m_value,然后基类的m_value初始化为2333333,而派生类的m_value初始化为666666

从输出结果可以看出,通过derived创建的objobj.m_value使用的是派生类的m_value。如果想使用基类的m_value,就要指明用的是基类的m_value,如:obj.base::m_value

建议:由于封装是面向对象设计的思想之一,所以一般很少会暴露出成员变量。尤其像上面这样,基类和派生类使用重名成员变量并且都是public,基本上不会有这样的设计。

基类和派生类重名的成员函数

基础示例

#include <iostream>
#include <string>

class stringex : public std::string
{
public:
    void append(const std::string &str);
};

int main(void)
{
    stringex text;
    text.append(std::string("ab"));
    text.append(std::string("13"));
    text.append(std::string("43"));
    text.append(std::string("ef"));
    text.append(std::string("66"));
    std::cout << text << std::endl;
    return 0;
}

void stringex::append(const std::string &str)
{
    if (empty())
    {
        std::string::append(str);
    }
    else
    {
        std::string::append(std::string("::") + str);
    }
}

输出结果:

ab::13::43::ef::66

基础讲解

派生类stringex的成员函数append()的作用是:每添加一个字符串,就会在这些字符串之间用::隔开。这个append()的函数名称和参数都跟基类std::string的一样。主要看一下成员函数append()的内容。

empty()函数是从基类继承下来的,然后直接调用即可。上面代码中的stringex重新定义了基类std::stringappend(str)函数,如果需要调用std::stringappend(str),那么就需要写成std::string::append(str);,如果省略了前一部分而写成append(str);,那么它就会调用自身而变成了一个递归函数。

通过派生类对象使用基类成员函数,可以参考成员变量的做法。

基础拓展

上面代码中的stringex重新定义了基类std::stringappend(str)函数,这个行为叫做重载(英文名:overload)。而覆盖(英文名:override)将在下一篇教程中讲解。

现在探讨一下,在继承中,派生类的成员函数跟基类的成员函数的名称相同的问题。这里有三种情况:

  1. 成员函数名称相同,参数不同,这个是重载
  2. 成员函数名称相同,参数也相同,这个也是重载
  3. 使用关键字virtual修饰,成员函数名称相同,参数也相同,这个是覆盖

下面讲解一下,重名的成员变量成员类型重载的成员函数的情况:

在派生类中:对于基类的私有成员,由于派生类不能访问和使用,所以不需要考虑。对于基类的受保护成员和公共成员,如果派生类需要使用基类的重名的成员,需要在使用的基类成员名称前指明使用的是基类的成员而不是本类的成员。因为按照规定,在类中调用函数或者变量等,优先调用该类的成员,如果没有的话再调用基类或者全局的,如果都没有就会编译报错。

使用对象时:对于私有成员和受保护成员都不能访问和使用,所以不需要考虑。通过对象使用公共成员时只能使用派生类的公共成员。特殊情况,如果想调用基类的公共成员,需要先用基类的引用或者指针来保存派生类的对象,然后再操作,这样就会只调用基类的成员;或者在调用的时候像上面示例那样指明基类。

如果在派生类中设计出重名公共成员函数,那么这个派生类就不是一个良好的设计,因为很容易使调用者出错,除非它是虚函数。关于虚函数的内容将在下一篇教程中讲解。


Comments

Make a comment

  • Index

WARNING: You are using an old browser that does not support HTML5. Please choose a modern browser (Chrome / Microsoft Edge / Firefox / Sarafi) to get a good experience.