分享

C++之继承基础

 山峰云绕 2023-05-03 发布于贵州

什么是继承

https://www.toutiao.com/article/7227338986254025277/?log_from=ddcf91675bf45_1683119951210

在C++编程中,继承是指派生一个新类以重用现有类的属性和方法。派生类(或子类)从一个或多个基类(或父类)继承属性和方法,从而可以扩展或修改基类的功能以满足新的需求。继承实现了“is-a”关系,即派生类是其基类的一种类型。

C++单继承

C++中,单继承是指一个派生类只从一个基类继承。这意味着一个类只有一个直接的基类,但可以有多个间接的基类。单继承有利于维护代码的简洁性和可读性,并且能够避免由于多继承所带来的一些问题,例如名称歧义和冲突等。当一个类被其他类所派生时,它的成员和访问限定符的使用及继承方式及其成员被合并到派生类中,使派生类可以通过继承基类的成员来重用现有的代码,同时又可以为派生类添加新的特性和功能。在单继承情况下,可以使用基类指针或引用来访问基类的成员,也可以通过派生类对象来访问派生类中所定义的成员。

单继承要点| 继承基本语法

C++继承基本语法如下:

class 基类 
{
public:
};
class 派生类 :继承方式 基类 
{

};

在C++中,有三种派生方式:公有继承(public inheritance)、私有继承(private inheritance)和保护继承(protected inheritance)。公有继承表示派生类继承了基类的public成员和protected成员,并且这些成员仍然可以在派生类内外进行访问;私有继承表示派生类继承了基类的public和protected成员,但这些成员在派生类外部都不可访问,只能在派生类内部使用;保护继承表示派生类继承了基类的public和protected成员,但这些成员在派生类外部不能访问,只能在派生类内部和派生类的子类中使用。

单继承要点| 继承实质

C++中继承实质是基类的属性和方法在子类中也会存在一份。但因为继承方式的不同,在子类中的权限是不同。主要如下表

继承中权限测试如下代码

#include <iostream>
#include <string>
//父类
class MM
{
public:
  int age;
  std::string getName()
  {
    return name;
  }
protected:
  int num;
private:
  std::string name;
};
//公有继承
class Boy1 :public MM
{
public:
  void Visited()
  {
    std::cout << age << std::endl;
    std::cout << num << std::endl;
    //父类中的私有属性不能直接访问
    //std::cout << name << std::endl;
    //可通过父类中的方法间接访问
    std::cout << getName() << std::endl;
  }
  //std::string getName() 
  //int age;
protected:
  //int num;
private:
  //std::string name;  //不能直接访问
};
//保护继承
class Boy2 :protected MM
{
public:

protected:
  //int age;
  //std::string getName()
  //int num;
private:
  //禁止直接访问
  //std::string name;
};

//私有继承
class Boy3 :private MM
{
public:
  void Visited()
  {
    std::cout << age << std::endl;
    std::cout << num << std::endl;
    //父类中的私有属性不能访问
    //std::cout << name << std::endl;
    //std::cout << getName() << std::endl;
  }
protected:

private:
  //int age;
  //std::string getName()
  //int num;
  //禁止直接访问
  //std::string name;
};

//继承的属性会一致存在,继承一般不适合写太多层数?
//导致子类很臃肿
class A
{
public:
  int a;
};
class B :public A
{
public:
  int b;
  //int a
};
class C :public B
{
public:
  int c;
  //int b;
  //int a;
};
int main()
{
  Boy1 b1;
  b1.age = 123;
  Boy2 b2;
  //最低权限就是protected 
  //b2.age = 123;
  return 0;
}

单继承要点| 派生类构造函数

在C++中,派生类的构造函数可以调用其基类的构造函数来初始化从基类继承而来的数据成员和属性。派生类构造函数在创建派生类对象时,首先调用基类构造函数来初始化基类部分,然后再初始化派生类自身的成员。派生类构造函数必须使用初始化列表来调用基类构造函数(调用父类无参构造函数可以不写,默认调用)

子类写无参构造函数,父类必须存在无参调用形态的构造函数(无参或者函数缺省),如下测试代码

#include <iostream>
#include <string>
class Parent
{
public:
  Parent()
  {
    std::cout << "Parent" << std::endl;
  }
};
class Son :public Parent
{
public:
  //子类写无参构造函数,父类必须存在无参调用形态的构造函数
  Son()
  {
    std::cout << "Son" << std::endl;
  }
};
int main()
{
  //构造子类之前先调用父类构造
  Son son;
  return 0;
}

运行结果如下

使用初始化参列表的方式调用父类的带参构造函数,如下测试代码

#include <iostream>
#include <string>
class Parent{
public:
  Parent(std::string name):name(name){
  }
  std::string name;
};
class Son :public Parent{
public:
  //子类写无参构造函数,父类必须存在无参调用形态的构造函数
  Son(std::string name,int age):Parent(name){
    //自身属性写法无限制
    this->age = age;
  }
  void print() {
    std::cout << name << "\t" << age << std::endl;
  }
  int age;
};
int main(){
  //构造子类之前先调用父类构造
  Son son("李四",18);
  son.print();
  return 0;
}

运行结果如下

通常为了使用方便,一般父类的构造函数会采用函数缺省的方式实现,这样可以满足子类无参构造的需求(例如创建对象数组)。

C++多继承

多继承是指一个类可以同时从多个父类(基类)继承成员变量和成员函数的能力。

多继承要点| 基本语法

在C++中,如果一个派生类需要从多个基类中继承成员变量和成员函数,可以使用逗号“,”分隔多个基类来实现多继承。即class 子类名: 继承方式 父类1, 继承方式 父类2,……

如下测试代码:

#include <iostream>
#include <string>
class MM{
public:
};
class GG 
{
public:
};
class Son :public MM,protected GG
{
public:
};
int main(){
  return 0;
}

当然继承方式根据具体需要具体选择。

多继承要点| 派生类构造函数

C++多继承派生类构造函数写法和单继承一样的,只需要把每一个父类的构造函数采用初始化参数列表的方式调用即可。同理如果子类想要构造无参对象,所有的父类中都要具备无参调用形态的构造函数。如下测试代码

#include <iostream>
#include <string>
class MM
{
public:
  MM()
  {
    std::cout << "MM()" << std::endl;
  }
  MM(std::string name1, std::string name2) :fMM_name(name1), sMM_name(name2)
  {

  }
protected:
  std::string fMM_name;
  std::string sMM_name;
};
class GG
{
public:
  GG()
  {
    std::cout << "GG()" << std::endl;
  }
  GG(std::string name1, std::string name2) :fGG_name(name1), sGG_name(name2)
  {

  }
protected:
  std::string fGG_name;
  std::string sGG_name;
};
class Son :public GG, public MM
{
public:
  Son()
  {
    std::cout << "Son()" << std::endl;
  }
  Son(std::string fGG_name, std::string sGG_name,
    std::string fMM_name, std::string sMM_name) :MM(fMM_name, sMM_name), GG(fGG_name, sGG_name)
  {
    this->fSon_name = fGG_name + fMM_name;
    this->sSon_name = sGG_name + sMM_name;
  }
  void print()
  {
    std::cout << "父:" << fGG_name + sGG_name << std::endl;
    std::cout << "母:" << fMM_name + sMM_name << std::endl;
    std::cout << "子:" << fSon_name + sSon_name << std::endl;
  }
protected:
  std::string fSon_name;
  std::string sSon_name;
};
int main()
{
  Son son;
  Son boy("欧", "皇", "阳", "倪");
  boy.print();
  return 0;
}

运行结果如下

多继承要点| 虚继承

C++虚继承也叫菱形继承,假设有一个类A,它有两个子类B和C,B和C都继承自A。现在又有一个类D,它继承自B和C。此时,如果A中有一个成员变量或者成员函数,在B和C中分别采用了不同的实现,那么在D中就出现了两个不同的实现。如果在D中要调用A的成员函数,就会有二义性,即不确定该调用哪个实现。如下图所示:

为了解决这个问题,C++中引入了虚继承概念。在上述例子中,如果从B和C继承A时采用虚继承方式,那么在D中只会有一个A的实现,这样就避免了二义性问题。

祖父类中属性必须调用祖父类的构造函数完成初始化,并且之只和祖父类有关,和其他父类无关,如下测试代码

#include <iostream>
#include <string>
//虚继承
class A
{
public:
  A(int a) :a(a)
  {
    std::cout << "A(a)" << std::endl;
  }
  int a;
};
//虚继承
class B :virtual public A
{
public:
  B(int a) :A(a)
  {
    std::cout << "B(a)" << std::endl;
  }
};
class C :virtual public A
{
public:
  C(int a) :A(a)
  {
    std::cout << "C(a)" << std::endl;
  }
};
class D : public B, public C
{
public:
  //虚继承一定要调用祖父类的构造函数
  //虚继承中,祖父类的构造函数之构造一次
  D(int a) :B(1), C(2), A(a)
  {
    std::cout << "D(a)" << std::endl;
  }
  void print()
  {
    //只存在一个,所有的a都是同一个a,并且只由祖父类的构造函数决定
    std::cout << B::a << std::endl;
    std::cout << C::a << std::endl;
    std::cout << A::a << std::endl;
    std::cout << D::a << std::endl;
  }
};
int main()
{
  D d(3);
  d.print();
  return 0;
}

程序运行结果如下:

需要注意的是,在虚继承中,由于需要通过虚基类指针访问虚基类的成员变量和成员函数,因此会产生额外的开销。同时,虚继承也增加了程序的复杂性,因此只有在必要的情况下才应该使用虚继承。

多继承要点| 构造顺序和析构顺序问题

继承中优先调用父类构造函数,再调自身构造函数,多继承中,父类构造顺序只和继承顺序有关,和初始化参数列表无关,如下测试代码

#include <iostream>
#include <string>
class A
{
public:
  A(int a) :a(a)
  {
    std::cout << "A(a)" << std::endl;
  }
  int a;
};
class B 
{
public:
  B(int a) 
  {
    std::cout << "B(a)" << std::endl;
  }
};
class C
{
public:
  C(int a) 
  {
    std::cout << "C(a)" << std::endl;
  }
};
class D : public A,public B, public C
{
public:
  D() :C(1), B(2), A(3) 
  {
    std::cout << "D(a)" << std::endl;
  }
};
int main()
{
  D d;
  return 0;
}

程序运行结果如下:

析构顺序和构造顺序,这里就不在重述,可以自行测试。

C++继承中同名问题

正常情况写代码,继承中一般很少出现同名问题,除了在使用多态的时候,同名问题主要是就是数据成员同名和成员函数同名,主要遵循原则如下

  • 正常的对象调用同名属性或者函数,类中类外都是就近原则,那个类的对象调用那个类
  • 非常的对象指针初始化,没有virtual看指针类型,存在多态看对象父类指针被子类对象初始化: 允许ok子类对象被父类初始化:不安全,会有隐患
  • 想要强制性的调用父类或者子类中的函数,可以采用类名限定的方式: 类名::数据成员名或者类名::成员函数名

如下测试代码

#include <iostream>
#include <string>
class MM
{
public:
  MM(std::string name) :name(name) {}
  void print()
  {
    std::cout << "MM" << std::endl;
  }
  void printMM()
  {
    std::cout << "printMM" << std::endl;
  }
protected:
  std::string name;
};
class Boy :public MM
{
public:
  Boy(std::string name) :name(name), MM("mm")
  {

  }
  void print()
  {
    std::cout << "Boy" << std::endl;
  }
  void printData()
  {
    std::cout << "------------" << std::endl;
    //就近原则
    print();      //调用Boy
    MM::print();    //调用父类中的
    std::cout << "------------" << std::endl;
  }
protected:
  std::string name;
};

int main()
{
  //正常初始化情况
  Boy boy("boy");
  boy.printData();
  //就近原则
  boy.print();
  boy.MM::print();
  boy.Boy::print();
  Boy* pBoy = new Boy("Son");
  pBoy->print();
  MM* pMM = new MM("MM");
  pMM->print();
  //非正常初始化情况
  MM* pGirl = new Boy("Son");
  //没virtual 看指针类型
  pGirl->print();
  //错误, 不允许直接new操作
  //Boy* pSon = new MM("Girl");
  MM mm("girl");
  //强上
  Boy* pSon = (Boy*)(&mm);
  pSon->print();
  //子类没有,父类有的,被继承了
  pSon->printMM();
  //调用子类中父类没有的
  pSon->printData();
  return 0;
}

相关

如果阁下正好在学习C/C++,看文章比较无聊,不妨关注下关注下小编的视频教程,通俗易懂,深入浅出,一个视频只讲一个知识点。视频不深奥,不需要钻研,在公交、在地铁、在厕所都可以观看,随时随地涨姿势。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多