C++继承

继承本质是类层次的复用。

 

学生和老师的类中包含Person类的内容。 

Print函数也是继承下来的。 

 

 上述代码中,Peraon叫父类,也叫基类,Student叫子类也叫派生类。

public叫继承方式,继承方式包括公有保护和继承。

 基类中的私有本质是不想被继承。

父类中的私有成员,子类中不能访问。父类中的保护成员,子类可以访问。

 

 但是age在studen类中,就是不能被访问。

基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。

去掉继承方式,class默认是私有继承,子类外部不能访问继承成员,struct默认是公有继承,子类外部能访问继承成员。

 子类会先构造父类,然后构造子类,析构时先析构子类,再析构父类。


父类和子类赋值转换。

子类对象 可以赋值给 父类的对象 / 父类的指针 / 父类的引用。 

int main()
{
Person p;
Student s;
p=s;
}

其中p=s中间不存在类型转换,没有产生临时变量。

而底下这个i=d会产生临时变量,临时变量是int类型。

 所以底下没有const修饰的引用会报错。在前面加上const就好了。

 父类 ++后,子类也++

 赋指针也行,指针指向子类中的父类部分。

  •  继承中的作用域

下面这个_num访问的是Student本身的_num

 访问父类的话可以指定作用域。

 子类和父类有同名成员时,子类隐藏了父类成员。

 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,
也叫重定义。 


两个fun构成隐藏关系。 

构成隐藏关系。

父类和子类函数名相同就构成隐藏。

重载要求必须在同一个作用域。 两个fun不构成重载。

class A
{
public:
	void fun()
	{
		cout << "A::func()" << endl;
	}
};
class B : public A
{
public:
	void fun(int i)
	{
		cout << "B::func(int i)->" << i << endl;
	}
};

void Test()
{
	B b;
	b.fun(10);
};

 如果去掉fun调用时的参数,就会编译报错。

因为两个fun构成隐藏关系,去掉fun的参数,就要访问A中的fun,但因为被隐藏,直接访问不行。

class A
{
public:
	void fun()
	{
		cout << "A::func()" << endl;
	}
};
class B : public A
{
public:
	void fun(int i)
	{
		cout << "B::func(int i)->" << i << endl;
	}
};

void Test()
{
	B b;
	b.fun();
};

这样改就行了。 

 建议继承中不要定义同名成员。


  • 派生类的默认成员函数

为什么调用Student会调用Person中的函数并执行?

子类中,父类成员调用父类构造函数完成初始化。

 

 父类没有默认构造函数会报错。

规定不能在子类初始化列表直接初始化父类成员变量。

 必须要调用父类的成员函数。

 下面看看拷贝构造。

父类部分调用父类拷贝构造。

 而如果是自定义类型,需要我们写个拷贝构造。

我们看到父类中拷贝构造函数参数是父类。

 在这里如何找到要拷贝的Person对象?

 我们直接传s就可以, 

子类传给父类发生了赋值转换。s传的就是Person中的内容。 

 

s赋值给p 不会产生临时对象。而是把s中p的部分切出来后赋值给p。但需要调用父类的拷贝构造。

rp可以引用s。 因为中间没产生临时变量,是直接切出来的。

我们来看看赋值。

我们不写,也会默认调用父类赋值。

显示写就这样写。 

 

 但这样运行会报栈溢出错误。

因为这个赋值和父类赋值构成隐藏关系,这里面的operator=(s)调用的是自己,所以会栈溢出。

这样改就好了。


 派生类的默认成员函数

 这样写析构会错。

这样就对了

因为子类析构函数和父类析构函数构成隐藏关系(由于多态关系需求,所有析构函数都会特殊处理成destructor函数名) 

而且这里面显示写父类析构的话,父类析构会调用两次。

调用两次的后果是,如果这样写就会报错。

 为了让子类先析构父类后析构,子类析构函数不需要显示调用父类析构函数,子类析构后回自动调用父类析构。


  • 继承和友元

友元关系不能继承。

 在子类中也声明友元就好了。


父类中的普通成员,子类和父类生成的 对象中这两个成员不是同一个,而父类中的静态成员,子类和父类生成的 对象中这两个成员是同一个。

 静态成员属于整个类,所有对象,同时也属于所有派生类及其对象。

而且可以这样访问。

 

 这个父类中的静态count也可以这样初始化,但很不建议这样用。

再看下面的例子

 第一个ptr->_name中,ptr是解引用,因为_name在对象中,而第二个ptr就不是解引用,因为成员函数不存在对象中。成员函数在代码段中。

ptr会传递一个空的this指针。

此时,下面划线这三行中,第一行会打印空指针,第二行会报错,第三行会正常打印。

 下面这样写也不会报错。因为它都是通过类去寻找的。


  •  多继承

一个子类有两个或以上直接父类时称这个继承关系为多继承。

 

 菱形继承有数据冗余和二义性的问题,Assistant对象中Person成员会有两份。

 指定作用域可以进行初步解决。

 但我们没有彻底地解决这个问题。

我们可以用关键字virtual进行虚继承。

 下面这样写,_name就是一个了。

如果不虚继承的话B和C里面的_a是不一样的。

 如果B和C虚继承A后,下面的_a就是同一个l。

 那么红线部分的是什么呢?

 我们用内存窗口查看一下。

 我们发现这是他们地址相差的数值。它是距离虚基类的偏移量。

B C就是虚基类。

 


  • 继承和组合

它俩都进行了复用,但是使用的复用方式不一样。

组合中,M的保护成员N不能看到。

继承是白箱复用,组合是黑箱复用。

但是组合有一个较大的优势是耦合度较低。

比如说X和M中各有80个保护成员和20个公有成员,那么X中的任何一个成员调整可能都会影响Y,而M中只有公有成员调整会影响N。

继承像is-a,比如学生是个人,而组合像has-a,比如头上有眼镜。

如果继承和组合都可以使用那么优先选择组合,因为它耦合度低。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南种北李

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值