继承【C++】

概念

概念

继承是面向对象程序使代码可以复用的手段,它允许在保持原有类特性的基础上进行扩展,增加功能,产生新的类,称为派生类或子类,被继承的类叫作基类或父类,继承是类设计层次的复用

格式

class Person
{
public:
	void Print()
	{
		cout << "name: " << _name << endl;
		cout << "age: " << _age << endl;
	}

protected:
	string _name = "Bob";
	int _age = 20;
};

// 继承后Person类的成员函数和成员变量都会成为子类的一部分

class Student : public Person
{
protected:
	int _stuid = 100000; // 学号
};

class Teacher : public Person
{
	int _jobid = 100000; // 工号
};

int main()
{
	Student s;
	Teacher t;
	s.Print();
	t.Print();

	return 0;
}

在这里插入图片描述

继承基类成员访问方式的变化

在这里插入图片描述

总结:

  1. 在实际运用中一般使用都是public继承,几乎很少使用protected/private继承,也不提倡使用protected/private继承,因为protected/private继承下来的成员都只能在派生类的类中使用,实际中扩展性不强
  2. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式
  3. 如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected,可以看出protected关键字是因继承才出现的

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

  • 派生类对象可以赋值给基类的对象/基类的指针/基类的引用,这种做法叫作切片或切割
  • 基类对象不能赋值给派生类对象

继承2

class Person
{
protected:
	string _name = "Bob";
	string _sex = "男";
	int _age = 20;
};

class Student : public Person
{
protected:
	int _stuid = 00000; // 学号
};

int main()
{
	Student stuobj;
	// 1. 派生类对象可以赋值给基类对象/指针/引用
	Person perobj = stuobj;
	Person* pp = &stuobj;
	Person& rp = stuobj;

	// 2. 基类对象不能赋值给派生类对象
	//stuobj = perobj; // 会报错

	return 0;
}

继承中的作用域

  • 在继承体系中基类派生类都有独立的作用域
  • 当基类和派生类有同名成员(成员变量同名或成员函数函数名相同)时,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏或重定义(在子类成员中,可以使用基类::基类成员显示访问)
  • 在实际的继承体系中最好不要定义同名成员
// Student的_num和Person的_num构成隐藏关系
class Person
{
protected:
	string _name = "Bob";
	string _sex = "男";
	int _num = 200000; // 身份证号
};

class Student : public Person
{
public:
	void Print()
	{
		cout << "姓名: " << _name << endl;
		cout << "身份证号: " << Person::_num << endl;
		cout << "学号: " << _num << endl;
	}

protected:
	int _num = 10000000; // 学号
};

int main()
{
	Student s;
	s.Print();
}

派生类的默认成员函数

构造函数:先调用基类的构造函数,再调用派生类的构造函数。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用(先父后子)

拷贝构造和赋值运算符:先调基类的再调派生类的

**析构函数:**先调用派生类的析构函数再调用基类的构造函数(先子后父)

class Person
{
protected:
	Person(const char* name = "Bob")
		: _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;
	}

private:
	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;
	}

private:
	int _num; // 学号
};

int main()
{
	Student s1("Bob", 20);
	Student s2(s1);
	Student s3("Becky", 19);

	s1 = s3;

	return 0;
}

友元函数和静态成员

  • 友元函数不能继承,也就是说基类友元不能访问派生类私有和保护成员
  • 基类定义了static静态成员,则整个继承体系中只有一个这样的成员。无论派生出多少个子类, 都只有一个static成员示例

菱形继承及菱形虚拟继承

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

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

菱形继承:菱形继承是多继承的一种特殊情况

在上述菱形继承的例子中,D类继承了B类和C类,B类和C类分别继承自A类,这就会导致D类中存放了两份A类的成员,导致了数据冗余和二义性问题,二义性问题可以用指定B类或C类的作用域解决,但数据冗余无法解决

class A
{
public:
	int _a = 1;
};

class B : public A
{
public:
	int _b = 2;
};

class C : public A
{
public:
	int _c = 3;
};

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

int main()
{
	// 由于二义性无法明确访问的是哪一个
	D d;
	//d._a = 1; // 报错
	
	// 通过指定作用域可以解决二义性问题,但无法解决数据冗余问题
	d.B::_a = 2;
	d.C::_a = 3;

	return 0;
}

虚拟继承可以解决菱形继承的二义性和数据冗余问题。如上例所示,在B类和C类继承A类时使用虚拟继承,即可解决问题。但是虚拟继承不要再其他地方使用

class A
{
public:
	int _a = 1;
};

class B : virtual public A
{
public:
	int _b = 2;
};

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

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

int main()
{
	D d;
	d._a = 1; // 不会报错

	return 0;
}

在实际编写代码的过程中不要使用菱形继承

总结

  1. 不建议设计多继承,不要设计菱形继承,否则复杂度和性能上都有问题
  2. 继承和组合
  • public继承是一种is-a的关系,也就是说每个派生类对象都是一个基类对象
  • 组合是一种has-a的关系,假设B组合了A,每个B对象都有一个A对象
  • 优先使用对象组合,而不是类继承
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值