继承(简单易懂)

继承的概念:

父辈的钱财(优点)被子孙继承。(就是这么简单)

一切空大的文字都是无力的,只有代码才是真。

class Person//基类(父类)
{
protected:
	string _name;
	string _sex;
	int age;
};
class Student : public Person//派生类(子类)
{
public:
	int _No;//学号
};
//这样就可以在Student依然拥有Person所有成员变量

补充一些知识:在父类中public表示对子类和类外公开,protected表示对子类公开但是对子类和自己以外私有,private表示仅对自己公开,子类和类外为私有、

为了让你更加明白父类与子类的关系,我制作了一下表格和帮助记忆的条款:

 个人认为条款比表格更加实用

表格 

继续补充:

可能你们会好奇为什么class Student : public Person为什么使用public,可以不给吗?

答案是:可以,但是如果class不给public则出现不能使用父类一切成员函数(变量),而struct则默认是全部公有不给public依然可以使用,也就是说class默认为private

赋值

子类和父类之间的赋值兼容规则:

子类对象可以赋值给父类对象/指针/引用,这个过程叫作切割或者切片

int main()
{
	Person P;
	Student s;

	P = s;
	Person* ptr = &s;
	Person& ref = s;
    //父类对象赋值给子类对象只有在特殊条件下可以,只有为什么会在多态说明
	return 0;
}

重定义

当子类有相同名字的函数or成员变量时会隐藏父类的函数or成员变量

class A
{
public:
	void fun()//因为与子类相同名字的函数,会被隐藏
	{
		cout << "hello world" << endl;
	}
};

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

 你会发现,如果你需要调用父类的fun()时不能直接调用,必须要声明它来自父类也就是A::fun()才可以调用,否则会报错

 在学习派生(子)类的默认成员函数之前我们应当炒一下冷饭

6个默认成员函数
初始化和清理:1、构造函数主要完成初始化工作
                         2、析构函数主要完成清理工作
       拷贝复制:3、拷贝构造时使用同类对象初始化创建对象
                          4、赋值重载主要是把一个对象赋值给另一个对象
    取地址重载:5、主要是普通对象和const对象去地址,这两个很少会自己实现

子类默认成员函数

class Person
{
public:
	Person(const char* name="pater")//构造
		:_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 stuid)//构造
		:Person(name),//只能调用父类的成员函数来处理父类的成员初始化等工作
		_stuid(stuid)
	{
		cout << "Student()" << endl;
	}
	Student(const Student& s)//拷贝
		:Person(s),
		_stuid(s._stuid)
	{
		cout << "Student(const Student& s)" << endl;
	}
	Student& operator=(const Student& s)
	{
		if (this != &s)
		{
			Person::operator=(s);//如果直接写operator=(s),编译器已经隐藏了父类赋值
            //他会以为是递归,会导致无限递归栈溢出
			_stuid = s._stuid;
			cout << "Student& operator=(const Student& s)" << endl;
		}
		return *this;
	}
   //子类的析构函数和父类的析构函数构成隐藏
   //因为他们的名字会被编译器统一处理成destructor(跟多态有关系)
	~Student()
	{
		//Person::~Person();//正确用法不需要你显示调用,结束好会自动调用父类
		cout << "~Student()" << endl;//结束后会自动调用~Person()
		//应该像在栈中一样,因为先进入Person(),后进入Student()
       //所以应该先出Student()后出Person()
	}
protected:
	int _stuid;
};

友元:

友元关系不能继承,也就是说父类友元不能访问子类私有和保护成员

//该代码无法运行,因为友元在父类中是无法使用的
class Student;
class Person
{
public:
	friend void Display(const Person& p.const Student& s);//友元
protected:
	string _name;//姓名
};
class Student : public Person
{
protected:
	int _stuNum;//学号
};

void Display(const Person& p, const Student& s)
{
	cout << p._name << endl;
	cout << s._studNum << endl;
}
int main()
{
	Person p;
	Student s;
	Display(p, s);
}

继承和静态变量

//继承与静态成员
class Person
{
public:
	Person()
	{
		++_count;
	}
	string _name;//姓名
	static int _count;//统计人的个数
};
int Person::_count = 0;

class Student :public Person
{
protected:
	int _stuNum;//学号
};

//静态变量存在公共区,无论子类还是父类改变都是永久性的
int main()
{
	Person p;
	Student s;
	p._name = "jack";
	s._name = "rose";

	p._count = 1;
	s._count = 2;

	++Person::_count;
	cout << Person::_count << endl;//输出3
	cout << s._count << endl;//同样输出3
	return 0;
}

复杂的继承及虚拟继承

单继承:一个子类只有一个总结父类时称这个继承关系为单继承

 

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

菱形继承:棱形继承是多继承的一种特殊情况
问题:数据冗余,二义性

 如果直接使用菱形继承,会导致student和Teacher都有Person的属性,例如一个研究生他即是学生又是助教,但是他只有一个姓名,而非虚继承的会有两个名字导致数据冗余或二义性,当你使用虚拟继承后就可解决该问题,先看代码。

class Person
{
public:
	string _name;//姓名
};

//虚拟继承
class Student:virtual public Person//想要解决菱形继承只能使用虚拟继承virtual
{
public:
	int _num;//学号
};
class Teacher :virtual public Person
{
public:
	int _id;//职工编号
};
class Assistant:public Student,public Teacher
{
protected:
	string _majorCourse;//主修课程
};

void main()
{
	Assistant a;
	a._name = "pater";

	//需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余无法解决
	a.Student::_name = "xxx";
	a.Teacher::_name = "yyy";
}

 接下来我将从内存的角度告诉你虚拟继承是如何解决该问题的。

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

class C :virtual public A
{
public:
	int _c;
};

class D :public B, public C
{
public:
	int _d;
};、

int main()
{
	D d;
	cout << sizeof(d) << endl;//非虚拟继承大小为20,虚拟继承大小为24

	d.B::_a = 1;//非虚拟继承B,C类有单独_a的空间,两个赋不同的值互不影响
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;

	return 0;
}

 内存的模样

 大家可能会很好奇,为什么需要存放指针,指针内为何存放在从_a到指针的偏移量。这是为了子类给父类赋值时可以找到_a,例如:

int main()
{
	D d;
	A a;
	a._a = 10000;
	d=a;
	return 0;
}

a中的_a会找到d中的A::_a对应指针,用偏移量加上地址找到_a后进行赋值

如今已经把所有的知识全部表述完成,我想我们应该做一些面试题来提升和检验一下自己的能力

面试题:

1、设计一个不能被继承的类

解答:

将构造函数和析构函数放到private中即可

2、平时使用继承还是组合?

解答:

is-a关系:car与奔驰和宝马的关系(继承)
hsa-a关系:tire(轮胎)和car的关系(组合)
都符合就使用组合

笔试题(自己写吧)
1、什么是菱形继承?菱形继承的问题是什么?
2、什么是菱形虚拟继承?如何解决数据冗余和二义性的
3、继承和组合的区别?什么时候用继承?什么时候用组合?

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值