【C++篇】继承和派生

news2024/11/14 5:36:04

友情链接:C/C++系列系统学习目录

知识总结顺序参考C Primer Plus(第六版)和谭浩强老师的C程序设计(第五版)等,内容以书中为标准,同时参考其它各类书籍以及优质文章,以至减少知识点上的错误,同时方便本人的基础复习,也希望能帮助到大家
 
最好的好人,都是犯过错误的过来人;一个人往往因为有一点小小的缺点,将来会变得更好。如有错漏之处,敬请指正,有更好的方法,也希望不吝提出。最好的生活方式就是和努力的大家,一起奔跑在路上


文章目录

  • 🚀一、继承和派生
    • ⛳(一)概述
      • 🎈1.派生和继承的实现
      • 🎈2.派生类(子类)对象的内存分布
    • ⛳(二)派生类和基类之间的特殊关系
      • 🎈1.访问权限:继承和派生的三种方式
      • 🎈2.指针指向
    • ⛳(三)子类
      • 🎈1.子类的构造函数
      • 🎈2.子类的析构函数
      • 🎈3.子类型
    • ⛳(四)多重继承
      • 🎈1.用法
      • 🎈2.多重继承的弊端:二义性
      • 🎈3.虚基类


🚀一、继承和派生

⛳(一)概述

父亲“派生”出儿子,儿子“继承”自父亲

从一个类派生出另一个类时,原始类称为基类,继承类称为派生类。派生和派生,本质是相同的,只是从不同的角度来描述。

在这里插入图片描述

🎈1.派生和继承的实现

在这里插入图片描述

Father.h

#pragma once
#include <string>

using namespace std;

class Father
{
public:
    Father(const char*name, int age);
    ~Father();
    	
    string getName();
    int getAge();
    string description();
private:
    int age;
    string name;
};

Father.cpp

#include "Father.h"
#include <sstream>
#include <iostream>


Father::Father(const char*name, int age)
{
    cout << __FUNCTION__ << endl;
    this->name = name;
    this->age = age;
}

Father::~Father()
{
}

string Father::getName() {
	return name;
}
int Father::getAge() {
	return age;
}

string Father::description() {
    stringstream ret;
    ret << "name:" << name << " age:" << age;
    return ret.str();
}

Son.h

#pragma once
#include "Father.h"
class Son : public Father {
public:
    Son(const char *name, int age, const char *game); //派生类需要自己的构造函数。
    ~Son();
    
    string getGame();
    string description();
private:
	string game;
};	

Son.cpp

#include "Son.h"
#include <iostream>
#include <sstream>

// 创建Son对象时, 会调用构造函数!
// 会先调用父类的构造函数, 用来初始化从父类继承的数据
// 再调用自己的构造函数, 用来初始化自己定义的数据
Son::Son(const char *name, int age, const char *game) : Father(name, age) {
    cout << __FUNCTION__ << endl;
    // 没有体现父类的构造函数, 那就会自动调用父类的默认构造函数!!!
    // 如果父类没有默认构造函数,如此例,那就需要使用初始化列表,用儿子的两个参数来显示调用父类构造函数进行初始化
    this->game = game;
}

Son::~Son() {
}

string Son::getGame() {
	return game;
}

string Son::description() {
    stringstream ret;
    // 子类的成员函数中, 不能访问从父类继承的private成员
    ret << "name:" << getName() << " age:" << getAge()
    << " game:" << game;
    return ret.str();
}

main.cpp

#include <iostream>
#include "Father.h"
#include "Son.h"

int main(void) {
    Father wjl("王健林", 68);
    Son wsc("王思聪", 32, "电竞");
    
    cout << wjl.description() << endl;
    
    // 子类对象调用方法时, 先在自己定义的方法中去寻找, 如果有, 就调用自己定义的方法
    // 如果找不到, 就到父类的方法中去找, 如果有, 就调用父类的这个同名方法
    // 如果还是找不到, 就是发生错误!
    cout << wsc.description() << endl;
    system("pause");
    return 0;
}
  • 除了“构造函数”和“析构函数”,父类的所有成员函数,以及数据成员,都会被子类继承!

  • 子类一般会添加自己的数据成员和成员函数, 或者, 重新定义从父类继承的方法!!! 子类对象就会调用自己重新定义的方法, 不会调用父类的同名方法,是一种静态的多态,即在派生类中重新定义基类的方法。还有一种动态的多态:虚方法,在后面会讲

    注意:

    1. 如果子类调用父类的同名方法,方法中涉及到的数据也是父类中的并不是子类中的

    2. 重新定义从父类继承的方法并不是重载,即如果重新定义继承的方法,应确保与原来的原型完全相同,只是函数体实现不同,如果原型不同,例如:

      class Father
      {
      public:
      	virtual void showperks(int a) const;
      ...
      };
      
      class Son : public Father {
      public:
      	virtual void showperks() const;
      ...
      };	
      

      新定义将showperks( )定义为一个不接受任何参数的函数。重新定义不会生成函数的两个重载版本,而是隐藏了接受一个int参数的基类版本。

  • _FUNCTION_ 获取函数签名,假如它是一个成员函数,它的类名和const/volatile限定符也将是签名的一部分。

🎈2.派生类(子类)对象的内存分布

vs使用技巧:

设置 vs 编译器,在命令行中添加选项(打印指定类的内存分布):

/d1 reportSingleClassLayoutFather /d1 reportSingleClassLayoutSon

在这里插入图片描述

重新生成:

在这里插入图片描述

测试:

int main(void) {
    Father wjl("王健林", 68);
    Son wsc("王思聪", 32, "电竞");


    cout << sizeof(wjl.age) << endl;
    cout << sizeof(wjl.name) << endl;

    cout << sizeof(wjl) << endl; // 48

    cout << endl;

    cout << sizeof(wsc.age) << endl;
    cout << sizeof(wsc.name) << endl;
    cout << sizeof(wsc.getGame()) << endl;

    cout << sizeof(wsc) << endl; // 88

    system("pause");
    return 0;
}

在这里插入图片描述

在这里插入图片描述

  • 注意涉及到内存对齐
  • 成员函数不占用对象的内存空间,但是也被子类继承了!!!

⛳(二)派生类和基类之间的特殊关系

🎈1.访问权限:继承和派生的三种方式

(1)public(公有)继承 [使用最频繁]

父类中定义的成员(数据成员和函数成员)被继承后,访问权限不变!

  • public --> public

  • protected --> protected

  • private --> private

(2)private(私有)继承

父类中定义的成员(数据成员和函数成员)被继承后,访问权限都变成 private

  • public --> private

  • protected --> private

  • private --> private

(3)protected(保护)继承

  • public --> protected

  • protected --> protected

  • private --> private

小结:public 继承全不变,private 继承全变私,protected 继承只把 public 降级为 protected

子类对父类成员的访问权限:

无论通过什么方式(public、protected、private)继承,在子类内部均可访问父类中的 public、protected 成员。

private 成员不可访问(如果想要子类能够访问,就定义为 protected),使用构造函数不能直接设置继承的成员,而必须使用基类的公有方法来访问私有的基类成员。具体地说,派生类构造函数必须使用基类构造函数。

继承方式只影响外界通过子类对父类成员的访问权限。

  • public 继承,父类成员的访问权限全部保留至子类;

  • protected 继承,父类 public 成员的访问权限在子类中降至 protected;

  • private 继承,父类 public、protected 成员的访问权限在子类中均降至 private。

在这里插入图片描述

🎈2.指针指向

基类指针可以在不进行显式类型转换的情况下指向派生类对象,那么同样,基类引用可以在不进行显式类型转换的情况下引用派生类对象:

Father father("陈七",20);
Son son("陈启",18,"王者");

Father * man = &son;
Father & man = son;
  • 注意:基类指针或引用只能用于调用基类方法,这涉及到之后要讲的多态

  • 通常,C++要求引用和指针类型与赋给的类型匹配,但这一规则对继承来说是例外。然而,这种例外只是单向的,不可以将基类对象和地址赋给派生类引用和指针

    上述规则是有道理的。例如,如果允许基类引用隐式地引用派生类对象,则可以使用基类引用为派生类对象调用基类的方法。因为派生类继承了基类的方法,所以这样做不会出现问题。如果可以将基类对象赋给派生类引用,将发生什么情况呢?派生类引用能够为基对象调用派生类方法,这样做将出现问题。例如,将方法Son::getGame() 用于Father对象是没有意义的,因为Father对象没有rating成员。

  • 在函数参数中使用派生类引用或指针,在传参时,可以传递基类对象,也可以传递派生类对象

  • 能够将基类对象初始化为派生类对象:

    Son son("陈七",18,"王者");
    Father father(son);
    

    该构造函数原型:

    Father(const Son &);
    

    类定义中没有这样的构造函数,但存在隐式复制构造函数:

    Father(const Father &);
    

    形参是基类引用,因此它可以引用派生类。这样,将father初始化为son时,将要使用该构造函数,它复制age和name成员。换句话来说,它将father初始化为嵌套在son中的Father对象。即用派生类对象初始化基类对象时,只有该派生类对象的基类部分会被拷贝,派生类部分将会被忽略掉。

    同样,也可以将派生对象赋给基类对象:

    Son son("陈七",18,"王者");
    Father father;
    father = son;
    

    在这种情况下,程序将使用隐式重载赋值运算符:

    Father & operatpr=(const Father&) const;
    
  • 在使用基类指针时,此指针调用的方法是基类的,即使指针指向子类,要使用子类的就涉及到多态问题

⛳(三)子类

🎈1.子类的构造函数

调用父类的哪个构造函数

class Son : public Father {

public:

    // 在子类的构造函数中,显式调用父类的构造函数
    Son(const char *name, int age, const char *game):Father(name, age) {
        this->game = game;
    }

    // 没有显式的调用父类的构造函数,那么会自动调用父类的默认构造函数
    Son(const char *name, const char *game){
        this->game = game;
    }

    ......
};
  • 创建派生类对象时,程序首先创建基类对象。从概念上说,这意味着基类对象应当在程序进入派生类构造函数之前被创建。C++使用成员初始化列表语法来完成这种工作。例如:

    此例中,需要提供两个相同的参数const char *name, int age来使用父类的构造函数,即Father(name, age) 是成员初始化列表,调用Father构造函数,Son构造函数将实参"王思聪"和2赋给形参name,age,然后将这些参数作为实参传递给Father构造函数,后者将创建一个嵌套Father对象,并将数据"王思聪"和2存储在该对象中。然后,程序进入Son构造函数体,完成Son对象的创建,并将参数game的值赋给game成员

  • 如果省略成员初始化列表,必须首先创建基类对象,如果不调用基类构造函数,程序将使用默认的基类构造函数,除非要使用默认构造函数,否则应显式调用正确的基类构造函数。

  • 也可以使用复制构造函数来作为初始化列表:

    RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer & tp):TableTennisPlayer (tp)
    {
    	rating = r;
    }
    

    这里将TableTennisPlayer的信息传递给了TableTennisPlayer构造函数:

    TableTennisPlayer (tp)
    

    首先,由于tp的类型为TableTennisPlayer &,因此将调用基类的复制构造函数。基类没有定义复制构造函数,但编译器将自动生成一个。在这种情况下,执行成员复制的隐式复制构造函数是合适的,因为这个类没有使用动态内存分配(string成员确实使用了动态内存分配,但本书前面说过,成员复制将使用string类的复制构造函数来复制string成员)。

1.总之呢,构建子类必须要有个父类,要继承父类初始化好的数据,根据参数列表中使用哪种父类的构造函数,就需要在子类构造函数中提供对应的参数,

  • 比如 Father(name, age)就需要提供const char *name, int age,子类构造函数体中管自己的game就行了,
  • 或者TableTennisPlayer (tp)使用拷贝构造函数,就需要提供const TableTennisPlayer & tp参数,到时候传递个已经构造好的父类进来就行,
  • 再或者不用参数化列表,会调用默认构造函数,参数只有一个自己的game就行

如果要想子类继承和我们定义好的父类实例一样的数据,可以都使用默认构造函数,或者子类的参数列表使用拷贝构造函数

2.实际上的初始化列表:

子类构造函数(数据类型 数值1,数据类型 数值2):父类构造函数(),变量名1(数值1),变量名2(数值2){}

  • 在构造函数执行时,先执行初始化列表,实现变量的初始化,然后再执行函数内部的语句
  • 成员初始化的顺序只与声明的顺序有关,而跟初始化列表的顺序无关。
  • 当类成员中有引用和const常量时就一定得初始化,否则会报错,

3.注意:

先使用父类构造函数初始化父类数据成员,以便于子类继承,但只是初始化数据,并未创建一个父类实例对象(此问题暂定)

4.当使用已有子类对象来初始化子类对象时:

前面讲的初始化方式都是普通的创建子类,所以都是涉及到关于构造函数以及它的初始化列表使用哪种父类构造函数先初始化父类,但是这里就涉及初始化方式采用已有子类对象初始化,涉及到的是子类的拷贝构造函数,所以我们应在子类的拷贝构造函数中使用初始化列表方式,先决定使用哪种构造函数方式初始化父类

  • 同样,如果不指定,会先调用父类的默认构造函数。再调用子类的拷贝构造函数

  • 显示调用父类拷贝构造函数,则是先调用父类拷贝构造函数, 再调用子类的拷贝构造函数:

    Son::Son(const Son& z):Father(z)
    {
        cout << "子类拷贝构造函数被调用";
        game = z.game;
    }
    

注意:我们使用子类构造函数时候,一般只会设置自己类的值,而不管从父类继承下来的值,这可能会导致我们拷贝时,最后的子类当中从父类继承下来的值不符合我们的预期,如下图,我们拷贝后的wsc1的age和name与wsc并不一样

因为我们并没有在子类拷贝构造函数中设置这两个值,用的是父类构造函数继承下来的两个值

Father::Father()
{
    cout << "父类默认构造函数被调用" << endl;
    this->name = "陈三";
    this->age = 18;
}

Son::Son(const Son& z)
{
    cout << "子类拷贝构造函数被调用";
    game = z.game;
    //name = z.name;  这样过后就不会有问题了
    //age = z.age;
}

int main(void) {
    Son wsc("王思聪", 32, "电竞");

    Son wsc1 = wsc;
    cout << wsc1.age << endl << wsc1.name << endl << wsc1.getGame() << endl;

    system("pause");
    return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J40pLML6-1687363674600)(E:\create\图片\C++\1\42.png)]

子类和父类的构造函数的调用顺序

当创建子类对象时, 构造函数的调用顺序:

静态数据成员的构造函数 -> 父类的构造函数 -> 非静态的数据成员的构造函数 -> 自己的构造函数

注意:

无论创建几个对象, 该类的静态成员只构建一次, 所以静态成员的构造函数只调用 1 次!!!

#include <iostream>
#include <Windows.h>

using namespace std;

class M {
public:
    M() {
    	cout << __FUNCTION__ << endl;
    }
};

class N {
public:
    N() {
    	cout << __FUNCTION__ << endl;
    }
};

class A {
public:
    A() {
    	cout << __FUNCTION__ << endl;
    }
};
	

class B : public A {
public:
    B() {
    	cout << __FUNCTION__ << endl;
    }
private:
    M m1;
    M m2;
    static N ms;
};

N B::ms; //静态成员


int main(void) {
    B b;
    system("pause");
}

执行:

  • N::N 静态数据成员的构造函数
  • A::A 父类的构造函数
  • M::M 非静态数据成员的构造函数
  • M::M 非静态数据成员的构造函数
  • B::B 自己的构造函数

🎈2.子类的析构函数

子类的析构函数的调用顺序,和子类的构造函数的调用顺序相反!!!记住,相反即可

#include <iostream>
#include <Windows.h>

using namespace std;

class M {
public:
    M() {
    	cout << __FUNCTION__ << endl;
    }
    
    ~M() {
    	cout << __FUNCTION__ << endl;
    }
};
		
class N {
public:
    N() {
    	cout << __FUNCTION__ << endl;
    }
    
    ~N() {
    	cout << __FUNCTION__ << endl;
    }
};

class A {
public:
    A() {
    	cout << __FUNCTION__ << endl;
    }
    
    ~A() {
    	cout << __FUNCTION__ << endl;
    }
};

class B : public A {
public:
    B() {
    	cout << __FUNCTION__ << endl;
    }
    ~B() {
    cout << __FUNCTION__ << endl;
}
private:
    M m1;
    M m2;
    static N ms;
};

N B::ms; //静态成员

int main(void) {
    {
    B b;
    cout << endl;
    }
    
	system("pause");
}

执行:

N::N
A::A
M::M
M::M
B::B

B::~B
M::~M
M::~M
A::~A

🎈3.子类型

公有继承时,派生类的对象可以作为基类的对象处理,派生类是基类的子类型。

在这里插入图片描述

#include <iostream>

using namespace std;
class A {
public:
    A() {}
    ~A() {}
    void kill() { cout << "A kill." << endl; }
};

class B : public A {
public:
    B(){}
    ~B(){}
    void kill() { cout << "B kill." << endl; }
};

void test(A a) {
	a.kill(); //调用的是A类对象的kill方法
}

int main(void) {
    A a;
    B b;
    test(a);  //A Kill
    test(b);  //A Kill
    
    system("pause");
    return 0;
}
  • 子类型关系具有单向传递性:C 类是 B 类的子类型而 B 类是 A 类的子类型,C 类也是 A 类的子类型

  • 如果把继承方式改为 protected 继承,或 private 继承,就会导致编译失败!

  • 在需要父类对象的任何地方, 可以使用”公有派生”的子类的对象来替代,从而可以使用相同的函数统一处理基类对象和公有派生类对象

    即:形参为基类对象时,实参可以是派生类对象(但反过来是不行的):

    #include <iostream>
    #include <sstream>
    
    using namespace std;
    
    class Father {
    public:
        void play() {
        cout << "KTV唱歌!" << endl;
    	}
    };
    
    class Son : public Father {
    public:
    	void play() {
    	cout << "今晚吃鸡!" << endl;
    	}
    };
    
    void party(Father *f1, Father *f2) {
        f1->play();
        f2->play();
    }
    
    int main(void) {
        Father yangKang;
        Son yangGuo;
        
        party(&yangKang, &yangGuo);  //都将执行KTV唱歌!
        
        system("pause");
        return 0;
    }
    
  • 子类型的应用:

    //1.基类(父类)的指针,可以指向这个类的公有派生类(子类型)对象:
    Son yangGuo;
    Father * f = &yangGuo;
    
    //2. 公有派生类(子类型)的对象可以初始化基类的引用
    Son yangGuo;
    Father &f2 = yangGuo;
    
    //3. 公有派生类的对象可以赋值给基类的对象
    Son yangGuo;
    Father f1 = yangGuo;
    

    如上所有的都是以父类为基础,都只能使用父类的相关方法,数据成员也只会将子类中包含的那个父类的所有数据给复制过来

⛳(四)多重继承

多继承/多重继承:

一个派生类可以有两个或多个基类(父类)。多重继承在中小型项目中较少使用,在 Java、C#等语言中直接取消多继承, 以避免复杂性.

🎈1.用法

将多个基类用逗号隔开.

实例:

例如已声明了类 A、类 B 和类 C,那么可以这样来声明派生类 D:

class D: public A, private B, protected C{
	//类 D 自己新增加的成员
};

D 是多继承形式的派生类,有 3 个父类(基类)它以公有的方式继承 A 类,以私有的方式继承 B 类,以保护的方式继承 C 类。根据不同的继承方式获取 A、B、C 中的成员.

多继承的构造函数:

D(形参列表): A(实参列表), B(实参列表), C(实参列表){
	//其他操作
}

Father.h

#pragma once
#include <string>

class Father
{
public:
    Father(const char *lastName="无姓", const char *firstName="无名");
    ~Father();

	void playBasketball(); //打篮球
protected:
    std::string lastName; //姓
    std::string firstName; //名
};

Father.cpp

#include "Father.h"
#include <iostream>

Father::Father(const char *lastName, const char *firstName)
{
    this->lastName = lastName;
    this->firstName = firstName;
}

Father::~Father()
{
}

void Father::playBasketball() {
	std::cout << "呦呦, 我要三步上篮了!" << std::endl;
}

Mother.h

#pragma once
#include <string>

class Mother
{
public:
    Mother(const char * food,const char *lastName = "无姓", const char *firstName = "无名");
    ~Mother();
    
    void dance();
private:
    std::string lastName; //姓
    std::string firstName; //名
    std::string food; //喜欢的食物
};

Mother.cpp

#include "Mother.h"
#include <iostream>

Mother::Mother(const char *food,const char *lastName, const char *firstName)
{
    this->food = food;
    this->lastName = lastName;
    this->firstName = firstName;
}

Mother::~Mother()
{
}

void Mother::dance()
{
	std::cout << "一起跳舞吧, 一二三四, 二二三四..." << std::endl;
}

Son.h

#pragma once
#include "Father.h"
#include "Mother.h"

class Son : public Father, public Mother {
public:
    Son(const char *lastName, const char *firstName,const char *food,const char *game);
	~Son();
	void playGame();
private:
	std::string game;
};

Son.cpp

#include "Son.h"
#include <iostream>

Son::Son(
    const char *lastName, const char *firstName,
    const char *food,
    const char *game)
	:Father(lastName, firstName), Mother(food)
{
	this->game = game;
}

Son::~Son()
{
}

void Son::playGame()
{
	std::cout << "一起玩" << game << "吧..." << std::endl;
}

main.cpp

#include <Windows.h>
#include "Son.h"

int main(void) {
    Son wsc("川菜", "王", "思聪", "电竞");
    wsc.playBasketball();
    wsc.dance();
    wsc.playGame();
    
    system("pause");
	return 0;
}

多继承的构造函数的调用顺序:

基类构造函数的调用顺序和和它们在派生类构造函数中出现的顺序无关,而是和声明派生类时基类出现的顺序相同:

//先调用Father的构造函数,再调用Mother的
class Son : public Father, public Mother 
{
}

🎈2.多重继承的弊端:二义性

如果我们在父亲类中增加一个跳舞方法:

Father.h

#pragma once
#include <string>

class Father
{
public:
    Father(const char *lastName="无姓", const char *firstName="无名");
    ~Father();

	void playBasketball(); //打篮球
    void dance();  //跳舞
protected:
    std::string lastName; //姓
    std::string firstName; //名
};

Father.cpp

#include "Father.h"
#include <iostream>

Father::Father(const char *lastName, const char *firstName)
{
    this->lastName = lastName;
    this->firstName = firstName;
}

Father::~Father()
{
}

void Father::playBasketball() {
	std::cout << "呦呦, 我要三步上篮了!" << std::endl;
}

void Father::dance() {
	std::cout << "嘿嘿, 我要跳霹雳舞!" << std::endl;
}

Son.h

#pragma once
#include "Father.h"
#include "Mother.h"

class Son : public Father, public Mother {
public:
    Son(const char *lastName, const char *firstName,const char *food,const char *game);
	~Son();
	
	void playGame();
	void dance();  //增加个跳舞方法
private:
	std::string game;
};

Son.cpp

#include "Son.h"
#include <iostream>

Son::Son(
    const char *lastName, const char *firstName,
    const char *food,
    const char *game)
	:Father(lastName, firstName), Mother(food)
{
	this->game = game;
}

Son::~Son()
{
}

void Son::playGame()
{
	std::cout << "一起玩" << game << "吧..." << std::endl;
}

void Son::dance() {
    Father::dance();
    Mother::dance();  //这里同时调用父母的dance()方法
    std::cout << "霍霍, 我们来跳街舞吧! " << std::endl;
}	

main.cpp

#include <Windows.h>
#include "Son.h"

int main(void) {
    Son wsc("川菜", "王", "思聪", "电竞");
    wsc.playBasketball();
    
    // 解决多重继承的二义性的方法1:
    // 使用 "类名::" 进行指定, 指定调用从哪个基类继承的方法!
    wsc.Father::dance();
    wsc.Mother::dance();

    // 解决多重继承的二义性的方法2:
    // 在子类中重新实现这个同名方法, 并在这个方法内部, 使用基类名进行限定,
    // 来调用对应的基类方法
    wsc.dance();

    wsc.playGame();
    
    system("pause");
	return 0;
}

🎈3.虚基类

多重继承除了在同名方法上存在二义性之外,在同名数据成员上也存在二义性问题

在这里插入图片描述

#include <iostream>
#include <string>
#include <Windows.h>
using namespace std;

// 电话类
class Tel {
public:
    Tel() {
    	this->number = "未知";
    }
protected:
	string number; //电话号码;
};

// 座机类
class FixedLine : public Tel {
};

// 手机类
class MobilePhone :public Tel {
};

// 无线座机
class WirelessTel :public FixedLine, public MobilePhone {
public:
    void setNumber(const char *number) {
        //this->number = number; //错误, 指定不明确
        this->FixedLine::number = number; //this可以省略
    }
    
    string getNumber() {
        //return MobilePhone::number;
        return MobilePhone::number;
    }
};

int main(void) {
    WirelessTel phone;
    phone.setNumber("13243879166");    //这里设置的是继承自FixedLine的number
    cout << phone.getNumber() << endl; //要获取继承自MobilePhone的number,打印未知
    system("pause");
    return 0;
}

座机和手机都继承自电话类,都继承了一个number数据成员,而无线座机有多重继承自座机和手机,将它们各自的number都继承了下来,以至于无线座机类有两个number数据成员,造成二义性

解决方法:

(1)使用基类名进行限定

(2)使用虚基类和虚继承:

#include <iostream>
#include <string>
#include <Windows.h>
using namespace std;

// 电话类
class Tel {
public:
    Tel() {
    	this->number = "未知";
    }
protected:
	string number; //电话号码;
};

// 座机类
class FixedLine : virtual public Tel {
};


// 手机类
class MobilePhone :virtual public Tel {
};

// 无线座机
class WirelessTel :public FixedLine, public MobilePhone {
public:
    void setNumber(const char *number) {
        this->number = number; //直接访问number
    }
    
    string getNumber() {
		return this->number;  //直接访问number
    }
};

int main(void) {
    WirelessTel phone;
    phone.setNumber("13243879166");   
    cout << phone.getNumber() << endl; 
    system("pause");
    return 0;
}
  • 座机和手机都虚继承自电话类,此时他们共同的基类,就成为虚基类
  • 而如果改成虚继承,由这两个派生出的子类就会只有一个number成员,从本质上说,两个子类对象共享一个number成员,

小结:尽量不要使用多重继承(多继承),部分高级语言已停止使用多重继承


行文至此,落笔为终。文末搁笔,思绪驳杂。只道谢不道别。早晚复相逢,且祝诸君平安喜乐,万事顺意。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/672624.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

不用手动改 package.json 的版本号

“为什么package.json 里的版本还是原来的&#xff0c;有没有更新&#xff1f;”&#xff0c;这个时候我意识到&#xff0c;我们完全没有必要在每次发布的时候还特意去关注这个仓库的版本号&#xff0c;只要在发布打tag的时候同步一下即可 node.js 部分&#xff0c;我们得有一个…

探索视频文本特征加速检索解决方案——倒排索引

前言 随着视频内容的不断增加&#xff0c;如何快速准确地检索到所需的视频成为了一个重要的问题。而视频文本特征加速检索解决方案——倒排索引&#xff0c;成为了解决这一问题的有效手段。该技术可以加速文本和视频片段特征匹配、相似度排序过程&#xff01; 定义——何为“…

windows -- dos命令

文章目录 内部命令变量常用命令特殊命令符综合案例 外部命令 内部命令 操作系统的内部命令。 win r, 输入cmd 打开命令窗口&#xff1b; 如&#xff1a; dir&#xff0c;查看当前目录下的内容cd&#xff0c;切换目录copy&#xff0c; 拷贝echo&#xff0c;打印 变量 windo…

硬盘被写保护怎么解决

目录 问题描述方法1&#xff1a;使用diskpart清除只读属性方法2&#xff1a;树莓派镜像烧录软件 U盘格式化不了&#xff0c;怎么做呢&#xff1f; 问题描述 方法1&#xff1a;使用diskpart清除只读属性 我是I盘出现了问题&#xff0c;所以我在命令提示符输入&#xff1a; chk…

Spring Cloud Alibaba Seata(一)

目录 一、Seata 1、分布式事务简介 1.1、分布式事务理论 1.2、分布式事务解决方案 2、Seata简介 3、Seata安装 一、Seata 1、分布式事务简介 基础概念&#xff1a;事务ACID A&#xff08;Atomic&#xff09;&#xff1a;原子性&#xff0c;构成事务的所有操作&#xf…

L0到L4级别下的泊车功能设计详解(上)

摘要&#xff1a; 乘用车自动驾驶/辅助驾驶按场景分主要包括城区场景、高速场景和泊车场景。 媳妇和我工作地点一南一北&#xff0c;工作地点公共交通又都不方便&#xff0c;在只有一辆车的背景下&#xff0c;我是早送仙女晚接美人&#xff0c;毫无怨言。但看到今年新车层出不…

Kali Linux 2023.2为Xfce版带来PipeWire支持

Kali Linux 2023.2为Xfce版带来PipeWire支持&#xff0c;彻底改造i3桌面&#xff0c;这个版本还引入了一个新的Hyper-V VM镜像&#xff0c;以及几个新的黑客工具。 Offensive Security宣布了他们流行的道德黑客和渗透测试GNU/Linux发行版的新版本&#xff0c;带来了新的功能&am…

chatgpt赋能python:Python整人代码:开发有趣的恶作剧工具

Python整人代码&#xff1a;开发有趣的恶作剧工具 Python是一种高级编程语言&#xff0c;它有着众多功能库和API&#xff0c;能够用于各种不同的领域。但是&#xff0c;Python也可以用来编写有趣的恶作剧代码&#xff0c;搞乐一下&#xff01;在这篇文章中&#xff0c;我们将介…

通过使用SpringBoot与ElementUI来实现数据的分页功能

背景 分页: 如果一次性的查询全部数据, 响应时间就太长了, 使得浏览器, java虚拟机都有延迟, 用户使用上就会容易出现卡顿:所以就要降低数据库的压力, 使用分页来显示, 一次显示一部分数据 例子: 假设有五条数据, 每一页都显示两条 实现分页要知道: 每页多少条数据当前的页数一…

C++ [STL容器适配器]

本文已收录至《C语言和高级数据结构》专栏&#xff01; 作者&#xff1a;ARMCSKGT STL容器适配器 前言正文容器适配器stack 栈stack的使用stack模拟实现 queue 队列queue的使用queue模拟实现 priority_queue 优先级队列priority_queue的使用priority_queue模拟实现 deque 双端队…

第二章 VGG网络详解

系列文章目录 第一章 AlexNet网络详解 第二章 VGG网络详解 第三章 GoogLeNet网络详解 第四章 ResNet网络详解 第五章 ResNeXt网络详解 第六章 MobileNetv1网络详解 第七章 MobileNetv2网络详解 第八章 MobileNetv3网络详解 第九章 ShuffleNetv1网络详解 第十章…

Debian12中Grub2识别Windows

背景介绍&#xff1a;windows10 debian11,2023年6月&#xff0c;Debian 12正式版发布了。抵不住Debian12新特性的诱惑&#xff0c;我将Debian11升级至Debian12。升级成功&#xff0c;但Debian12的Grub2无法识别Window10。于是执行如下命令&#xff1a; debian:~# update-grub G…

图片加载错误的捕获及处理

引言 前端开发中&#xff0c;图片是我们在网页中加载最多的静态资源类型之一&#xff0c;但是图片加载过程中也有可能出现加载失败的情况&#xff0c;这是十分影响用户体验的。那么如何正确的判断图片是否成功加载&#xff0c;以及图片加载失败的时候&#xff0c;如何处理&…

了解 Dockerfile 和搭建 Docker 私有仓库:让容器化部署变得更简单

目录 1、Dockerfile 1.1什么是Dockerfile 1.2常用命令 1.3使用脚本创建镜像 2、Docker私有仓库 2.1私有仓库介绍&#xff1a; 2.2私有仓库搭建与配置 2.3上传镜像到私有仓库&#xff1a; 1、Dockerfile 1.1什么是Dockerfile Dockerfile是由一些列命令和参数构成的脚本…

【服务器数据恢复】IBM存储分配的卷无法访问的数据恢复案例

服务器故障&#xff1a; 一台IBM DS存储出现故障&#xff0c;存储分配给aix小机的卷无法访问。从底层查看分配给aix小机的3个卷的lvm信息丢失。 服务器数据恢复过程&#xff1a; 1、将存储中所有磁盘编号后取出&#xff0c;以只读方式做全盘镜像&#xff0c;后续的数据分析和数…

【C++篇】字符串:标准库string类

友情链接&#xff1a;C/C系列系统学习目录 知识总结顺序参考C Primer Plus&#xff08;第六版&#xff09;和谭浩强老师的C程序设计&#xff08;第五版&#xff09;等&#xff0c;内容以书中为标准&#xff0c;同时参考其它各类书籍以及优质文章&#xff0c;以至减少知识点上的…

Eyeshot 2023 Added NuGet packages.

Added Microsoft Visual Studio 2022 Extensions menu item.Microsoft .NET 6 Windows Toolbox items.Added NuGet packages.Planar curve projection on Sketch plane.Improved fillet surfaces quality and speed.Added ICurve.ConverToLinearPath() family of methods.   …

RabbitMQ学习笔记4(小滴课堂)RabbitMQ工作队列模型实战

Java项目整合RabbitMQ 创建一个maven项目。 然后我们在maven里加上jdk和rabbitmq的依赖设置&#xff1a; 我们写一段生产者的代码&#xff1a; 然后我们去运行它&#xff1a; 可以看到这里有一个队列。 现在我们是可以查看到队列的。 我们去写消费者代码&#xff1a; 这里的之…

LeetCode 2090. K Radius Subarray Averages【前缀和,滑动窗口,数组】中等

本文属于「征服LeetCode」系列文章之一&#xff0c;这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁&#xff0c;本系列将至少持续到刷完所有无锁题之日为止&#xff1b;由于LeetCode还在不断地创建新题&#xff0c;本系列的终止日期可能是永远。在这一系列刷题文章…

Redis是什么,如何学习,如何整合SpringBoot?

目录 一、Redis是什么&#xff1f; 二、如何学习Redis 三、如何整合SpringBoot 一、Redis是什么&#xff1f; Redis 是一个高性能的开源 NoSQL 数据库&#xff0c;支持多种数据结构&#xff0c;包括字符串、哈希、列表、集合和有序集合等。它采用内存存储&#xff0c;可以快…