首页 > 其他分享 >【继承】讲解

【继承】讲解

时间:2024-10-27 23:20:23浏览次数:3  
标签:cout 继承 派生类 int 讲解 基类 public

访问控制

传递下去可以一共分为四个特性

  1. 公有
  2. 保护
  3. 私有
  4. 存在但不可见
    虽然它们各自的特性不同,能不能使用也另说,但是在建立类对象的时候,系统都会申请相应的内存,也就是说,无论它们能不能用,它们都存在。

公有继承

在这里插入图片描述

  1. 基类的私有数据成员存在但在派生类里不可见
    即不可直接使用:
    在这里插入图片描述
    在这里插入图片描述
    但可以间接利用公有成员函数访问:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

私有继承

在这里插入图片描述

  1. 基类的公有和保护变为派生类的私有,可以在派生里边直接用,但不能在主函数里边用,就和派生类原有的私有数据成员同样的特性。基类的私有在派生类里边存在但不可见,但是可以和上面的方法一样用函数间接使用。
    在这里插入图片描述

保护继承

  1. 保护数据成员是专门为基类和派生类之间的层次关系准备的,对外界来说,它和私有数据成员没什么区别。
    在基类与派生类之间调用保护类数据成员时,保护类数据成员和公有数据成员用法无异;但在主函数里,保护类数据成员不可见,而公有类数据成员可见,二者有异。
    在这里插入图片描述

重名成员(不难,随便看看就ok)

屏蔽和访问

1.重名数据成员

class  base
  { public :
           int  a ,  b ;  
  } ;
class  derived : public  base
  { public :  
         int  b ,  c ; 
  } ;
void  f ()
{ derived  d ;
   d . a = 1 ;
   d . base :: b = 2 ;//作用域控制符访问
   d . b = 3 ;
   d . c = 4 ;
};

2.重名成员函数

#include<iostream.h>
class A
{ public:	  
       int a1, a2 ;
      A( int i1=0, int i2=0 ) { a1 = i1; a2 = i2; }
      void print() 
         { cout << "a1=" << a1 << '\t' << "a2=" << a2 << endl ; }
};
class B : public A
{ public:	
       int b1, b2 ;
       B( int j1=1, int j2=1 ) { b1 = j1; b2 = j2; }
       void print()		//定义同名函数
         { cout << "b1=" << b1 << '\t' << "b2=" << b2 << endl ; }
      void printAB()
        { A::print() ;		//派生类对象调用基类版本同名成员函数
           print() ;		//派生类对象调用自身的成员函数
       }
};
void main()
{ B  b ;        b.A::print();	b.printAB();  }

继承过程中的静态成员

记住共享,无论是基类还是派生类只要它们的数据成员的名字相同,它们的数据成员的大小的改变就是同时的,数据是共享的,尽管不是同一个类里面的数据成员

#include<iostream>
using namespace std;
class B
{
public:
    static void Add() { i++; }
    //静态成员函数的功能主要是改变静态数据成员的大小
    static int i;//静态数据成员
    void out() { cout << "static i=" << i << endl; }
};
int B::i = 0;//初始化必须在类外
class D : private B
{
public:
    void f()
    {
        i = 5;//私有继承,i变成了类D的私有数据成员
        Add();//D的成员函数
        B::i++;//改变的是类B的公有数据成员,参考数据成员变量
        B::Add();//B的成员函数
    }
}; 
void main()
{
    B x;  D y;
    x.Add();//x.i=1;
    x.out();//
    y.f();//y.i=6;y.B::i=8
    //访问静态数据成员的两种不同的方式
    cout << "static i=" << ++(B::i) << endl;
    //用作用域符访问静态数据成员//8//9
    cout << "static i=" << x.i << endl;
    //用类对象访问静态数据成员//8//9
    //cout<<"static i="<<y.i<<endl;//错误,i是类D的私有数据成员,在主函数里边不可访问
}

在这里插入图片描述
在这里插入图片描述

基类的初始化

派生类继承了基类中除构造函数和析构函数之外的所有成员
基类的构造函数和析构函数不能被派生类所继承,派生类需要定义自己的构造函数和析构函数
在这里插入图片描述
由于在继承的过程中,基类的构造函数不会被继承,所以为了初始化基类的数据,C++提供了一种机制,可以通过在派生类对象初始化的时候利用初始化式调用基类的构造函数来进行对基类数据的初始化。
对于基类和派生类构造函数的执行顺序,不依照初始化式来进行,而是依靠继承路径来执行。
执行的顺序一般为,基类,对象数据成员,派生类本身。
直接基类和间接基类:
父类被称为子类的直接基类
父类的父类或更高层次的父类被称为这个子类的间接基类

#include<iostream>
#include<cstring>
using namespace std;
class people
{
public:
	char name[50];
	people(char* p) { strcpy(name, p); cout << "people " << p << " init" << endl; }
};
class student : virtual public people
{
public:
	char stunumber[10];
	student(char* p, char* q) : people(p)
	{ strcpy(stunumber, q); cout << "student " << p << " " << q << " init" << endl; }
};
class teacher : virtual public people
{
public:
	char ternumber[10];
	teacher(char* p, char* r) : people(p)
	{ strcpy(ternumber, r); cout << "teacher " << p << " " << r << " init" << endl; }
};
class teacher_student : public student,public teacher
{
public:
	teacher_student(char* p, char* q, char* r): people(p),student(p,q),teacher(p,r)
	{ cout << "teacher_student " << p << " " << q << " " << r << " init" << endl; }
};
int main()
{
	int i;
	cin >> i;
	if (i == 0) return 0;
	people p1("ZhangSan");
	student s1("LiSi", "01247012");
	teacher t1("WangWu", "83005");
	teacher_student ts1("ZhaoLiu", "01147011", "92002");
	return 0;
}

继承的应用实例

课本上主要举了圆柱体,圆,点之间的继承关系,还顺便把继承关系和包含关系比较了一把。下面就通过一个例题看吧:

class Base1
{ public :
Base1( int i )
{ cout << “调用基类Base1的构造函数:” << i << endl; }
int i;
};
class Base2
{ public:
Base2( int j )
{ cout << “调用基类Base2的构造函数:” << j << endl; }
};
class A : public Base1, public Base2
{ public :
A( int a,int b,int c,int d):Base2(b),Base1(c),b2(a),b1(d)
{ cout << “调用派生类A的构造函数:” << a+b+c+d << endl; }
private :
Base1 b1;
Base2 b2;
};
//
看冒号后边的继承顺序,并且按照先构造基类,
然后派生类数据成员,最后派生类的顺序。
先构造Base1,再构造Base2,然后对于类的成员,
就可以接着按照初始化的顺序来依次构造,(这里其实这两个成员用的就是包含的形式,
在A类里边包含了Base1,和Base2的类对象,
因此在外界访问的时候
(如果满足条件可以访问的话)
就得写成obj.b1.i)
//类的继承和包含最后实现的效果是一样的,
只是访问形式和数据的存储形式与等级不同
int main()
{ A obj( 1, 2, 3, 4 );
}

多继承

一个派生类仅有一个直接基类,称为单继承;
一个类可以从多个基类里边派生出来,即一个类有多个直接基类(例如一个物品具有多种特征),称为多继承。
在这里插入图片描述

多继承的派生类构造和访问

像上面的例题讲的一样的问题,就是由于这个派生类同时由许多基类产生,换句话说这个小孩有许多直接遗产需要继承。
然后就需要构造,构造的顺序是,先基类,(同时有很多基类的话就按照基类继承的顺序依次进行),然后派生类的数据成员这儿有点道道。。。见下方代码),最后是派生类本身(有时候要要注意虚继承的构造函数的结果)

#include<iostream>
using namespace std;
class D
{
public:
	D()
	{
		cout << "constructed" << endl;
	}
	~D()
	{
		cout << "deleted" << endl;
	}
};
class Base1 :public D
{
public:
	Base1(int i)
	{
		cout << "调用基类Base1的构造函数:" << endl;
	}
	~Base1()
	{
		cout << "调用基类Base1的析构函数" << endl;
	}
	int i;
};
class Base2 :  public D
{
public:
	Base2(int j)
	{
		cout << "调用基类Base2的构造函数:" << endl;
	}
	~Base2()
	{
		cout << "调用基类Base2的析构函数" << endl;
	}
};
class A : public Base1, public Base2
{
public:
	A(int a, int b, int c, int d) :Base2(b), Base1(c), b2(a), b1(d)
	//前两个基类的构造顺序是看public继承时候的特性,由于上面写的是先继承Base1,后继承Base2,所以结果是先构造Base1,再构造Base2;紧接着是派生类的两个类类型的数据成员,由于这两个类数据成员在private里的顺序,也就是作为该派生类数据成员的顺序,是先Base2,再Base1,因此先构造b1,再构造b2,也就是先Base2,再Base1
	{
		cout << "调用派生类A的构造函数:" << a + b + c + d << endl;
	}
	~A()
	{
		cout << "调用派生类A的析构函数" << endl;
}
private:
	Base2 b1;
	Base1 b2;
};
int main()
{
	A obj(1, 2, 3, 4);
}

在这里插入图片描述

虚继承

一个类不能被多次说明为一个派生类的直接基类,但可以不止一次地成为间接基类。
1.非虚继承
直接基类是上一级,间接基类是源头的那一级。
针对,D继承B1,B2,同时B1,B2又分别继承自B,由于是非虚继承,就导致在D类对象中会有两个继承B类的成员副本(静态成员函数就只有一个成员副本,因为它的共享特性),B是D的非虚基类。
而我们的本意是希望在派生类D的对象中只有一个B的成员副本,因为毕竟就只想继承一次间接基类B的成员副本,但是由于继承顺序的关系,先B1再B,先B2再B,这样就会执行两次间接基类B的构造函数。比如,在我们想调用B类的成员函数时,若D a,a.getdata();由于有B1从B类中继承过来的成员函数——getdata(),也有B2从B类中继承过来的成员函数——getdata(),直接像上面这样调用的话,会产生二义性,因此我们需要加上作用域。(转下文)
为了不两次调用非虚基类的构造函数,为了避免访问时产生的二义性,可以用作用域进行显示转换,但是很麻烦,所以就直接使用虚继承。
2.虚继承
只对基类对象的数据初始化一次,就要把B1和B2对B的继承说明为虚继承,在继承路径的时候,在类继承的关键字之前加上virtual。
也就是说B1,B2类虚继承B类,B是它们的虚基类。
因此一个类在类体系中可以作为虚基类或非虚基类,这取决于派生类对它的继承方式,而与基类本身的定义方式无关。因此,为了建立唯一的间接基类版本,应该声明派生类为虚继承基类,而不是声明间接基类为虚基类
因为如果直接声明间接基类为虚基类,也就是直接定义的话,这样根本就不知道到底是你哪个派生类,什么样的继承方式究竟是怎样的,对于消除二义性没有什么意思。
在多继承情况下,虚基类关键字的作用范围和继承方式关键字相同,只对紧随其后的基类起作用需要注意的是在第一级继承时就要将共同基类设置为虚基类。

#include<iostream>
using namespace std;
class a
{
public:
	a(int p=5,int q=6)
	{
		cout << "a" << endl;
		a1 = q;
	}
	int a1;
};
class b : virtual public a
{
public:
	b(int m=2,int n=3)
	{
		cout << 'b' << endl;
		b1 = n;
	}
	int b1;
};
class c : public b 
{
public:
	c(int x=0,int y=1,int z=10):b(1,x),a(1,y)
	{
		n = z;
	}
	int n;
};
int main()
{
	c c1;
	
	cout << c1.n << endl << c1.a1 << endl << c1.b1 << endl;
	return 0;
}
结果是
a b 10 1 0
//
	c(int x=0,int y=1,int z=10):b(x),a(y)
	只改初始化的这一处
	结果是
	a b 10 6 3

下方这两处的virtual也就只能减少一次在基类构造的次数,对于派生类的类数据成员构造的时候,并不会减少间接基类的构造。

#include<iostream>
using namespace std;
class D
{
public:
	D()
	{
		cout << "constructed" << endl;
	}
	~D()
	{
		cout << "deleted" << endl;
	}
};
class Base1 : virtual public D//
{
public:
	Base1(int i)
	{
		cout << "调用基类Base1的构造函数:" << endl;
	}
	~Base1()
	{
		cout << "调用基类Base1的析构函数" << endl;
	}
	int i;
};
class Base2 : virtual public D//
{
public:
	Base2(int j)
	{
		cout << "调用基类Base2的构造函数:" << endl;
	}
	~Base2()
	{
		cout << "调用基类Base2的析构函数" << endl;
	}
};
class A : public Base1, public Base2
{
public:
	A(int a, int b, int c, int d) :Base2(b), Base1(c), b2(a), b1(d)
	{
		cout << "调用派生类A的构造函数:" << a + b + c + d << endl;
	}
	~A()
	{
		cout << "调用派生类A的析构函数" << endl;
}
private:
	Base2 b1;
	Base1 b2;
};
int main()
{
	A obj(1, 2, 3, 4);
}

在这里插入图片描述

标签:cout,继承,派生类,int,讲解,基类,public
From: https://blog.csdn.net/weixin_50512050/article/details/116559515

相关文章

  • 基于数据可视化+SpringBoot+Vue的闲置物品一站式交易平台设计和实现(源码+论文+部署讲
    博主介绍:CSDN毕设辅导第一人、全网粉丝50W+,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌技术范围:SpringBoot、Vue、SSM、HLMT、J......
  • 基于数据可视化+SpringBoot+Vue的二手物品交易平台设计和实现(源码+论文+部署讲解等)
    博主介绍:CSDN毕设辅导第一人、全网粉丝50W+,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌技术范围:SpringBoot、Vue、SSM、HLMT、J......
  • 指针入门讲解
    一.指针的定义1.引入   1.指针是内存中一个最小单元的编号,也就是地址。   2.平时我们口头中说的指针是指指针变量。   总结:指针就是地址,口语中说的指针是指指针变量。内存地址一个字节0xFFFFFFFF一个字节0xFFFFFFFE............一个字节0x00000000  ......
  • Flutter 自定义组件继承与调用的高级使用方式
    ✅近期推荐:求职神器https://bbs.csdn.net/topics/619384540......
  • Python装饰器 - 具体讲解
    Python装饰器是一种非常有用的功能,它允许我们以不修改原有函数代码的方式,扩展或增强函数的行为。装饰器本质上是一个函数,它接收一个函数作为参数并返回一个新的函数。下面我将详细讲解Python装饰器的概念、用法和实现。1.装饰器的基本概念装饰器的基本语法如下:defdecor......
  • C++的继承和多态
    继承继承的本质意义是复用(不用写就有)父类的某些东西可以直接使用eg.但是注意:被继承的成员是新的成员,不是共用同一个成员(实例化的成员变量不同)         但是调用的函数是同一个函数继承基类成员访问方式的变化(重点)private访问 "在派......
  • ERP管理系统(源码+文档+部署+讲解)
    ERP管理系统、资源管理系统、业务流程管理系统、资源协同平台、决策支持系统、资源协同管理平台、资源规划与控制平台、资源信息管理系统、资源智能管理系统供应商本文将深入解析“ERP管理系统”的项目,探究其架构、功能以及技术栈,并分享获取完整源码的途径。系统概述ERP......
  • 物资管理系统(源码+文档+部署+讲解)
    物资管理系统、物料管理系统、物资采购管理系统、物资库存监控系统、物资分配优化系统、物资需求预测系统、物资成本控制系统、物资质量跟踪系统、物资供应协同平台、物资数据分析平台、物资智能调度系统供应商本文将深入解析“物资管理系统”的项目,探究其架构、功能以及技......
  • 仓储管理系统(源码+文档+部署+讲解)
    仓储管理系统、智能仓储管理平台、库存优化控制系统、仓库自动化管理系统、库存智能调配系统、仓储资源规划系统、智能库存监控系统、仓库作业执行系统、库存管理与分析系统、仓库管理智能平台、库存流转跟踪系统、智能供应链管理平台供应商本文将深入解析“仓储管理系统”......
  • 项目管理系统(源码+文档+部署+讲解)
    项目管理系统、项目管理分析平台、项目成本控制系统、项目交付平台、项目执行监控系统、项目沟通协作平台、项目需求管理系统、项目变更管理系统、项目质量监控系统、项目资源优化平台、项目风险管理平台、项目成本分析系统、项目进度监控系统、项目团队管理平台、项目任务......