继承

继承基础

​ 继承是在基类的基础上创建一个新的类型,例如:

class animal 
{
public:
    int age;
    char name[0x20];
};

class monkey : public animal 
{
public:
    float h; // 高度 
};

​ monkey类可以通过public权限来得到animal类中某些成员变量和成员函数,当计算sizeof(animal) 可以得到36,而计算sizeof(monkey) 可以得到40。可见通过继承monkey得到animal类中的内容。其中animal类叫做基类(父类),monkey类叫做派生类(子类),子类自动继承了包含父类的成员函数和成员变量。

​ 下面给出继承的基本形式:

class 派生类名 : 访问属性[public|private|protected] 父类名称
{};

// 若某个类不想被其他类继承,可以使用final关键字 
class 类名 final // 这样当前类就无法被其他类继承

​ 继承是可以连续的,即就像一代一代不断向下传递,举例:

class object
{
public:
    char Name[0x20];
};

class mapObject : public object
{
public:
    int x;
    int y;
};

class MoveObject : public mapObject 
{
public:
    int hp;
    int lv; 
};

​ 其中mapObject的基类为object,Moveobject的基类为mapObject。

继承权限

​ 下面给出类继承权限的详细表格,其中第一行的代表类中权限第一列代表继承时使用的访问权限关键字

访问权限publicprotectedprivate
publicpublicprotected继承但不能访问
protectedprotectedprotected继承但不能访问
privateprivateprivate继承但不能访问

​ 由此可以总计,无论时什么继承,基类的private权限内都不能访问。对于另外的情况,可以这样理解,把public看作3,protected看作2,private看作1,继承时遇到比自己大的就会将其改成自己。例如,public继承不会改变基类,protected继承会将public改成protected,而private会将public和protected都改成private。

​ 通过使用using 基类:: 变量(函数)名,可以修改基类对于变量或者函数的访问权限,但是这样会破坏类的封装性。对于函数,不需要写参数列表,只需要给出函数名即可。

class mapObject : public object
{
public:
    int x;
    int y;
protected:
    int test;
    int getx() { return x; }
private:
    int tttt;
};

class MoveObject : public mapObject 
{
public:
    using mapObject::test; // 将 protected 变成 public
    using mapObject::getx; 
    //using mapObject::tttt;
    int hp;
    int lv; 
};

​ 其中要注意的是,无法修改基类中private权限的成员函数或者变量。

继承中的构造函数

继承类中的构造顺序

​ 对于派生类的构造,其构造函数执行是从最上层的基类一直到当前类的构造函数。而对于副本构造函数来说,只有当前类会执行副本构造函数,而上层的基类还是会执行之前的默认构造函数。

class object
{
public:
    char Name[0x20];

    object()
    {
        cout << "objec构造" << endl; 
    }

    object(const object& obj)
    {
        cout << "objec副本构造" << endl;
    }
};

class mapObject : public object
{
public:
    int x;
    int y;

    mapObject()
    {
        cout << "mapObject构造" << endl;
    }

    mapObject(const mapObject& obj)
    {
        cout << "mapObject副本构造" << endl;
    }
};

class MoveObject : public mapObject 
{
public:
    int hp;
    int lv; 


    MoveObject()
    {
        cout << "MoveObject构造" << endl;
    }

    MoveObject(const MoveObject& obj)
    {
        cout << "MoveObject副本构造" << endl;
    }
};

int main() 
{
    MoveObject m;
    MoveObject m1 = m; 

    return 0;
}

​ 结果为:

image-20251113134015321

指定构造函数

​ 在派生类中可以指定要执行的基类构造函数,其具体形式就是:

派生类名(参数列表) : 基类名(参数列表)
{
    
}

​ 对之前的代码进行修改如下:

// 增加函数构造函数
mapObject(int _x, int _y) : x(_x), y(_y)
{
    cout << "mapObject含参构造" << endl; 
}

// 指定构造函数
MoveObject() : mapObject(10, 20)
{
    cout << "MoveObject构造" << endl;
}

MoveObject(const MoveObject& obj) : mapObject(obj)
{
    cout << "MoveObject副本构造" << endl;
}

​ 然后再次执行main函数中的测试得到结果如下:

image-20251113134654421

继承基类构造函数

​ 除了可以指定使用基类的构造函数外,还可以使用using 基类::基类构造函数来继承基类的构造函数,但是不会继承基类的默认构造函数和副本构造函数。要注意的是:使用这种方法继承基类构造函数会继承基类的(除默认构造函数和副本构造函数所有构造函数

​ 举例:

// 在MoveObject类中加入 由于是继承构造函数,无论放在public下还是private下都可以访问
using mapObject::mapObject;

// 加入基类的含参构造
MoveObject() 
{
    mapObject(10, 20);
    cout << "MoveObject构造" << endl;
}

// 修改,判断m的大小是否变化
int main() 
{
    MoveObject m;
    cout << sizeof(m) << endl;
    cout << "------------" << endl;
    MoveObject m1 = m; 

    return 0;
}

​ 在次执行main函数中测试代码可得:

image-20251113135218561

​ 分析一下,首先要构造MoveObject先构造了 object和mapobject,然后进入MoveObject构造函数,因为要执行mapObject的含参构造,先执行object的构造函数,然后执行完mapObject含参构造,最后执行完MoveObject构造函数。

继承中重名问题

​ 在继承中基类和派生类之间可能有成员变量和成员函数名字相同的情况。此时就需要了解如何解决这种问题。

​ 给出下面的类:

class object
{
public:
    int x;
};

class mapObject : public object
{
public:
    int x;
};

​ 若对于成员变量同名,对于派生类对象可以直接方法(局部原则),而如果需要访问基类的对象则需要通过基类作用域来访问:

mapObject obj; 

// 同名成员变量可以使用这样的方法访问
obj.x = 10;
obj.object::x = 20; 
cout << obj.x << ' ' << obj.object::x << endl;

​ 而对于函数名相同,又可以分为两种情况:

  • 函数结构完全相同,通过使用基类::函数名,这种形式来访问
  • 函数的参数不相同,通过使用using 基类::函数名,这种形式来访问,注意这种情况只在于当前派生类中没有基类的某个函数的相同格式情况下。

代码示例:

class object
{
public:
    void show()
    {
        cout << "objec的show函数" << endl;
    }
};

class mapObject : public object
{
public:
    // 继承基类中的show函数
    using object::show;

     void show()
    {
        cout << "mapObjec的show函数" << endl;
    }

    void show(int p)
    {
        cout << "mapObject的带参数show函数:" << p << endl; 
    }
};

class MoveObject : public mapObject 
{
public:
    void show()
    {
        cout << "objec的show函数" << endl;
    }
};

​ 在测试时,需要需要调object类中的show函数必须对mapObject(派生类)中的show进行注释,否则mapObject中的show函数会直接对基类的show函数进行覆盖,注释之后还需要使用using object::show 用来继承基类的show函数

​ 测试代码:

mapObject obj; 
// 下面show函数在基类和派生类中函数结构完全相同 
// 派生类的show函数
obj.show();
// 积累的show函数 
obj.object::show(); 

// 调用派生类的带参数的show函数
obj.show(10);


MoveObject mobj; 
mobj.show();

// 下面两种形式效果相同 调用最上层的基类中show函数
mobj.mapObject::object::show();
mobj.object::show();

多重继承

​ 多重继承中包含一些问题,其中例如成员变量和成员函数重名,重复继承相同基类的相同成员变量。

对于基类中变量重名的情况不再说明,直接使用基类::变量名的方法即可。

​ 下面给出代码示例然后进行解释:

class Wolf
{
public:
    void bite()
    {
        cout << "Wolf bite" << endl;
    }

    void eat()
    {
        cout << "Wolf eat" << endl; 
    }
};

class Man
{
public:
    void eat()
    {
        cout << "Man eat" << endl; 
    }
};

class WolfMan : public Wolf, public Man 
{
public:

};

​ 测试代码:

int main() 
{

    WolfMan Jack;

    Jack.eat();  // 报错,不知道调用基类中哪个eat函数

    return 0;
}

​ 对于这种函数重名的情况,可以使用在使用成员函数的时候,使用基类::函数名()的形式来进行调用

​ 修改过后:

int main() 
{

    WolfMan Jack;

    Jack.Wolf::eat(); 
    Jack.Man::eat(); 

    return 0;
}

​ 当然也可以自己设计变量来判断,然后再派生类中重写函数eat,然后进行调用

​ 修改过后:

class WolfMan : public Wolf, public Man 
{
public:
    bool change = false;
    void Change()
    {
        change = !change; 
    }

    void eat()
    {
        if (!change)
            Man::eat();
        else
            Wolf::eat(); 
    }
};

int main() 
{

    WolfMan Jack;

    Jack.eat(); // 人类形态 
    Jack.Change(); // 变身
    Jack.eat(); // 狼形态

    return 0;
}

​ 下面给出一种情况,就是Wolf和Man同时继承了一个Animal类,而Animal类中有变量x、y,此时WolfMan再构造时,就会同时构造Wolf和Man,这时Wolf类中有一对x和y,而Man类中也有一对x和y。当然如果我们想要访问直接使用基类::变量名即可,但是这样会操作变量重复创建,毕竟我们只需要一对x和y,而不是两对x和y。

​ 代码示例:

class Animal
{
public:
    int x;
    int y; 
};

class Wolf : public Animal
{
public:
    void bite()
    {
        cout << "Wolf bite" << endl;
    }

    void eat()
    {
        cout << "Wolf eat" << endl; 
    }
};

class Man : public Animal
{
public:
    void eat()
    {
        cout << "Man eat" << endl; 
    }
};

class WolfMan : public Wolf, public Man 
{
public:
    bool change = false;
    void Change()
    {
        change = !change; 
    }

    void eat()
    {
        if (!change)
            Man::eat();
        else
            Wolf::eat(); 
    }
};

​ 测试代码:

int main() 
{

    WolfMan Jack;

    Jack.Wolf::x = 100;
    Jack.Man::x = 200;

    cout << Jack.Wolf::x << endl;
    cout << Jack.Man::x << endl; 

    return 0;
}

​ 输出结果为:

100
200

​ 对于这种情况可以这样来理解,对于下面的图,可以看到WolfMan创建需要Wolf和Man两个对象都构建,因此会有两个x和y。

image-20251114143445060

​ 对于上面这种情况,该如何避免呢?

​ 可以使用虚继承,当出现这种菱形继承问题时,可以再中间的类中使用虚继承,从而防止最上层基类成员变量的重复使用。

class Wolf : public virtual Animal
{
public:
    void bite()
    {
        cout << "Wolf bite" << endl;
    }

    void eat()
    {
        cout << "Wolf eat" << endl; 
    }
};

class Man : public virtual Animal
{
public:
    void eat()
    {
        cout << "Man eat" << endl; 
    }
};

​ 测试代码:

int main() 
{

    WolfMan Jack;

    Jack.Wolf::x = 100;
    Jack.Man::x = 200;

    cout << Jack.Wolf::x << endl;
    cout << Jack.Man::x << endl;
    cout << Jack.x << endl; 

    return 0;
}

​ 输出:此时是输出的三个x都是200,且可以直接使用Jack.x进行访问。

疑问:使用虚继承和不适用虚继承的情况,sizeof(Jakc)结果都相同,这是为什么?

l Animal
{
public:
void bite()
{
cout << “Wolf bite” << endl;
}

void eat()
{
    cout << "Wolf eat" << endl; 
}

};

class Man : public virtual Animal
{
public:
void eat()
{
cout << “Man eat” << endl;
}
};


​	

​	测试代码:

```cpp
int main() 
{

    WolfMan Jack;

    Jack.Wolf::x = 100;
    Jack.Man::x = 200;

    cout << Jack.Wolf::x << endl;
    cout << Jack.Man::x << endl;
    cout << Jack.x << endl; 

    return 0;
}

​ 输出:此时是输出的三个x都是200,且可以直接使用Jack.x进行访问。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐