C++继承

继承概念及定义:

继承是用于类和类之间的,继承是为了代码的复用,是原有的类的功能基础上,功能又得到拓展,被拓展的类是派生类/子类,继承的类叫做基类/父类。
看代码说话。
继承定义格式:class B :public A
其中B是子类,A是基类。

class A
{
   public:
      int _a;
};
class B : public A
{
    public:
       int _b;
}

继承方式有:公有继承,保护继承,私有继承。
在这里插入图片描述
这里我们先定义A是基类,B是派生类。
若B是公有继承,对于B在自己类中能访问A中公有成员和保护成员,但在类外只能访问A的公有成员。
若B是保护继承,对于B只能在自己类中访问A中公有成员和保护成员,B在类外谁都访问不了。
若B是私有继承,对于B只能在自己类中访问A中公有成员和保护成员,B在类外谁都访问不了。
总的来说:基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。基类的所有私有成员都是不可见的。在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用。

派生类对象和基类对象之间的赋值转换

重要概念切片:派生类可以赋值给基类指针,基类引用,基类对象,此时会发生切片。看下面代码

class A
{
     private:
            _a;
};
class B  :public A
{
     private:
            _b;
}
void test()
{
      B boy;
      A girl;
      girl = boy;  // 这个是可以的,是将派生类对象赋值给基类对象会发生切片
      boy = girl;   //这个是不行的,基类的对象不能赋值给派生类对象。
      A *hihop = & boy; //这个可以,因为它是将派生类对象的地址传给基类指针,此时也发生切片。
      A &hip = boy;  //这个也可以,因为它是A基类引用了B派生类,此时也发生切片。
      //基类指针可以强制类型转换赋值给派生类指针。
      hiphop = &boy;
      B *p1 = (B *)hiphop;//这种情况是可以的
     hiphop = &girl;
      B *p2 = (B *)hiphop;  //这种情况可以,但可能会访问越界,因为此时的hiphop指针是gilr的地址,gilr是发生过切片的,此时强制类型转换相当于访问可能大于此时的容量,而发生访问越界。
}

继承中的作用域

隐藏:派生类会屏蔽基类同名成员的直接访问,这叫隐藏或者重定义。

class A
{
public:
	int func()  {}
	int _a;
};
class B :public A
{
public:
	int func() {
		_a = A::_a;
	}
	int _b;
	int _a;
};
void test()
{
	B b;
	b.A::func();
}

此时A类中的func()函数,_a和B类中同名的func()函数,_a构成隐藏。B类作用域中不可直接访问A中成员,如果要访问,必须声明基类的作用域。类外同样。这里要注意与重载区分,重载是在同一作用域。

派生类的默认构造函数

1.若想继承基类的成员和能调用派生类的成员函数:
派生类构造函数必须先在构造函数内调用基类的构造函数,此时基类的构造函数必须有参,派生类的构造函数中调用基类的构造函数可以无参,或者在初始化列表中显示调用基类的构造函数(此时基类的构造函数必须要有相应的参数),若基类没有在构造函数初始化相应的成员变量,则只能在初始化列表中显示调用基类的构造函数。
若此时派生类构造函数不调用基类的构造函数,此时的基类的成员必须得到初始化,初始化后,派生类只能调用基类的没有基类成员变量参与的成员函数,当然此时继承就没有了意义就失去了。
2.派生类的拷贝构造函数中必须调用基类的拷贝构造函数。
3.派生类的运算符重载也必须调用基类的相应的运算符重载
4.派生类的析构函数先完成自己的析构后在调用基类的析构函数,这样保证了建立栈祯时的顺序。

class Person
{
public :
Person(const char* name = "peter")
: _name(name )
{
	cout<<"Person()" <<endl;
}
Person(const Person& p)
: _name(p._name)
{
	cout<<"Person(const Person& p)" <<endl;
}
Person& operator=(const Person& p )
{
	cout<<"Person operator=(const Person& p)"<< endl;
	if (this != &p)
	_name = p ._name;
	return *this ;
}
~Person()
{
	cout<<"~Person()" <<endl;
}
protected :
string _name ; // 姓名
};
class Student : public Person
{
public :
Student(const char* name, int num)
: Person(name )
, _num(num )
{
	cout<<"Student()" <<endl;
}
Student(const Student& s)
: Person(s)
, _num(s ._num)
{
	cout<<"Student(const Student& s)" <<endl ;
}
Student& operator = (const Student& s )
{
	cout<<"Student& operator= (const Student& s)"<< endl;
if (this != &s)
{
	Person::operator =(s);
	_num = s ._num;
}
	return *this ;
}
~Student()
{
	cout<<"~Student()" <<endl;
}
	protected :
	int _num ; //学号
};
void Test ()
{
	Student s1 ("jack", 18);
	Student s2 (s1);
	Student s3 ("rose", 17);
	s1 = s3 ;
}

当然还有最重要的两点
1.基类的友元不能访问子类的私有和保护成员!!
2.基类定义了静态成员,则无论有多少个派生类,该静态成员只存在一份成员实例。

菱形继承及菱形虚拟继承

单继承:基类只被一个派生类继承。
多继承:一个派生类有两个或者两个以上的基类。
菱形继承:一个基类被多个子类继承,另一个类又继承了这几个子类。
在这里插入图片描述
在这里插入图片描述
我们能从上图中清晰的看到菱形继承的形式,可以明显看到菱形继承的两个缺点
1.数据的二义性
2.数据冗余。

class Person
{
	public :
		string _name ; // 姓名
};
class Student : public Person
{
	protected :
		int _num ; //学号
};
class Teacher : public Person
{
	protected :
		int _id ; // 职工编号
};
class Assistant : public Student, public Teacher
{
	protected :
		string _majorCourse ; // 主修课程
};
void Test ()
{
// 这样会有二义性无法明确知道访问的是哪一个
	Assistant a ;
	a._name = "peter";
// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
	a.Student::_name = "xxx";
	a.Teacher::_name = "yyy";
}

有人就会问,数据冗余有什么关系?
Assistant实例化对象,这个对象那么它要么是Teacher老师,要么是Student学生,其次这个对象是人,如果不解决数据冗余的问题,这个对象即是老师,又是学生,就不符合逻辑了。
数据冗余需要虚继承来解决。

class Person
{
	public :
		string _name ; // 姓名
};
class Student : virtual public Person
{
	protected :
		int _num ; //学号
};
class Teacher : virtual public Person
{
	protected :
		int _id ; // 职工编号
};
class Assistant : public Student, public Teacher
{
	protected :
		string _majorCourse ; // 主修课程
};
void Test ()
{
	Assistant a ;
	a._name = "peter";
}

那虚继承到底是怎么解决数据冗余的问题呢?
当Student和Techer虚继承了Person时,此时Student和Teacher会在自己的栈祯中最前端加入虚基表指针,此指针分别指向各自的虚基表,虚基表中记录了各自起始地址到父类Person的偏移量,然后通过偏移量找到Person相应的_name。需要注意的是,若Assistant 实例化对象指明Student域的_name值为10,然后Assistant 实例化对象指明Teacher域的_name值为20,此时,20会覆盖10,也是解决数据冗余最终想要得到的结果。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值