C/C++ 进阶(1)继承

个人主页:仍有未知等待探索-CSDN博客

专题分栏:C++

目录

一 、回顾封装

二、继承

1、什么是继承?

2、继承定义(格式)

3、继承方式和访问限定符

4、基类和派生类对象赋值转换 

 5、继承中的作用域

6、派生类的默认成员函数

7、友元继承

8、继承与静态成员

9、菱形继承及菱形虚拟继承

1、单继承

2、多继承

3、菱形继承(多继承的一种特殊情况)

4、虚拟继承 


一 、回顾封装

1、数据和方法放到一起,想访问的设置成公有,不想访问设置成私有。

2、一个类型放到另一个类型里面,通过typedef 成员函数调整,封装另一个全新的类型。

二、继承

1、什么是继承?

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。

对于不同的类,可能会有相同的属性,如果在每一个类中都存一份相同的属性,可能会属性冗余。这样我们就可以把相同的属性拿出来,单独设置成一个类,然后成为父类(基类)

2、继承定义(格式)

3、继承方式和访问限定符

 这个表格挺重要的!!!

理解:

1、基类(父类)的private成员在子类是不可见的,在类外也是不能访问的。

class person
{
public:
	person(string name = "张三", int age = 20)
		:_name(name)
		,_age(age)
	{}
	string& getname()
	{
		return _name;
	}
	int& getage()
	{
		return _age;
	}
private:
	string _name;
	int _age;
};
class student : public person
{
public:
	student(int sno = 0)
		:_sno(sno)
	{}
	void print()
	{
        // 子类内无法直接访问父类的私有成员
		//cout << _name << " " << _age << " " << _sno << endl;
        // 但是可以通过父类的成员函数进行获取父类的私有成员
        cout << getname() << " " << getage() << " " << _sno << endl; 
	}
private:
	int _sno;
};

2、protected:在派生类类能直接访问父类的protected成员(默认继承方式是public)

class person
{
public:
	person(string name = "张三", int age = 20)
		:_name(name)
		,_age(age)
	{}
protected:
	string _name;
	int _age;
};
class student : public person
{
public:
	student(int sno = 0)
		:_sno(sno)
	{}
	void print()
	{
        // 可以在子类内直接访问父类的protect成员
        // 在类外依旧是不能访问
		cout << _name << " " << _age << " " << _sno << endl;
	}
private:
	int _sno;
};

3、除了基类private成员,基类的其他成员在派生类的访问方式==Min(成员在基类的访问限定符,继承方式)。

public > protected > private

继承方式是public最常用,其他的了解即可

4、class继承方式和访问限定默认是私有,struct继承方式和访问限定默认是公有。

5、private是完全私有的,只有自己可以访问,派生类和外部都不可以访问(可以间接的使用,在派生类内)。

protected是受保护的,只有派生类可以访问,外部不能访问。

4、基类和派生类对象赋值转换 

1、派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。(赋值不产生临时对象

2、基类对象不能赋值给派生类。

3、基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使RTTI(RunTime Type Information)的dynamic_cast 来进行识别后进行安全转换。

class Person
{
protected :
	string _name; // 姓名
	int _age; // 年龄
};
class Student : public Person
{
public :
	int _sno ; // 学号
};
void Test ()
{
	Student sobj ;
	// 1.子类对象可以赋值给父类对象/指针/引用
	Person pobj = sobj ;
	Person* pp = &sobj;
	Person& rp = sobj;
	// 2.基类对象不能赋值给派生类对象
	sobj = pobj;
}

 5、继承中的作用域

1. 在继承体系中基类和派生类都有独立的作用域。
2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)
3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏
4. 注意在实际中在继承体系里面最好不要定义同名的成员

// B中的fun和A中的fun不是构成重载,因为不是在同一作用域
// B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。
class A
{
public:
	void fun()
	{
		cout << "func()" << endl;
	}
};
class B : public A
{
public:
	void fun(int i)
	{
		A::fun();
		cout << "func(int i)->" <<i<<endl;
	}
};

6、派生类的默认成员函数

1、派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。

2、派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。

3、派生类的operator=必须要调用基类的operator=完成基类的复制。

4、派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。

5、派生类对象初始化先调用基类构造再调派生类构造。(先父后子)

6、派生类对象析构清理先调用派生类析构再调基类的析构。(先自后父)

7、因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同(这个我们后面会讲解)。那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加virtual的情况下,子类析构函数和父类析构函数构成隐藏关系。

总结:在派生类中,要把基类当成一个整体。

7、友元继承

 友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。(不用考虑公有,子类公有的话,类外也能访问)

8、继承与静态成员

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子
类,都只有一个static成员实例。

9、菱形继承及菱形虚拟继承

1、单继承

一个子类只有一个直接父类时称这个继承关系为单继承。

2、多继承

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

3、菱形继承(多继承的一种特殊情况)

菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。在assistant的对象中person成员会有两份。
 

#include <iostream>
#include <string>
using namespace std;

class person
{
public:

//protected:
	string _name;
};
class student : public person
{
protected:
	int _sno;
};
class teacher : public person
{
protected:
	int _tno;
};
class assistant : public student, public teacher
{
protected:
	string _majorCourse;
};
int main()
{
	assistant a;
	// 二义性:不知道给student的_name赋值 还是 给teacher的_name赋值
	a._name = "peter";

	// 通过指定类域来解决二义性的问题,但是数据冗余无法解决
	a.teacher::_name = "peter";
	a.student::_name = "peter";
	return 0;
}

4、虚拟继承 

 虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和
Teacher的继承Person时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地方去使用。

注意:虚继承要写在”腰“上。

#include <iostream>
#include <string>
using namespace std;

class person
{
public:

//protected:
	string _name;
};
class student : virtual public person
{
protected:
	int _sno;
};
class teacher : virtual public person
{
protected:
	int _tno;
};
class assistant : public student, public teacher
{
protected:
	string _majorCourse;
};
int main()
{
	assistant a;
	// 二义性:不知道给student的_name赋值 还是 给teacher的_name赋值
	a._name = "peter";

	cout << a._name << endl;
	// 通过指定类域来解决二义性的问题,但是数据冗余无法解决
	a.teacher::_name = "peter";

	cout << a._name << endl;
	a.student::_name = "a";

	cout << a._name << endl;
	return 0;
}

对于普通的继承,d的内存结构:

#include <iostream>
#include <string>
using namespace std;

class A
{
public:
	int _a;
};
class B : public A
//class B : virtual public A
{
public:
	int _b;
};
class C : public A
//class C : virtual public A
{
public:
	int _c;
};
class D : public B, public C
{
public:
	int _d;
};
int main()
{
	D d;

	d.B::_a = 1;
	d.C::_a = 2;

	d._b = 2;
	d._c = 3;
	d._d = 4;

	return 0;
}

对于菱形虚拟继承,d的内存结构:

#include <iostream>
#include <string>
using namespace std;

class A
{
public:
	int _a;
};
//class B : public A
class B : virtual public A
{
public:
	int _b;
};
//class C : public A
class C : virtual public A
{
public:
	int _c;
};
class D : public B, public C
{
public:
	int _d;
};
int main()
{ 
	D d;

	d.B::_a = 1;
	d.C::_a = 2;

	d._a = 0;
	d._b = 2;
	d._c = 3;
	d._d = 4;

	cout << sizeof(d);
	return 0;
}

菱形虚拟继承中d的大小???(在X86下)

#include <iostream>
#include <string>
using namespace std;

class A
{
public:
	int _a;
};
//class B : public A
class B : virtual public A
{
public:
	int _b;
};
//class C : public A
class C : virtual public A
{
public:
	int _c;
};
class D : public B, public C
{
public:
	int _d;
};
int main()
{ 
	D d;

	d.B::_a = 1;
	d.C::_a = 2;

	d._a = 0;
	d._b = 2;
	d._c = 3;
	d._d = 4;

	cout << sizeof(d);
	return 0;
}

24。 

24 = 4 * 6

4个整型变量 + 2 两个虚基表指针。

谢谢大家!!!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

仍有未知等待探索

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

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

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

打赏作者

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

抵扣说明:

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

余额充值