C++进阶:继承(二)

72 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情

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

#include<iostream>
using namespace std;

class Person
{
public:
	/*void f()
	{}*/
protected:
	string _name;
	string _sex;
	int _age;
};
//class Student : protected Person
class Student : public Person
{
public:
	int _No;
};

int main()
{
	Person p;
	Student s;

	p = s;//父类对象 = 子类对象
	Person* ptr = &s;//父类指针 = 子类指针
	Person& ref = s;//父类引用 = 子类

	//s = p;//子类对象 = 父类对象,err
			//子类指针 = 父类指针,ok,但是最好用dynamic_cast,因为这样才是安全的,这里的安全指的是它会去识别父类的指针,如果是指向父类,这个转换就失败,如果是指向子类,这个转换就成功。
			//引用同指针

	return 0;
}
  • 我们都知道同类型的对象赋值是可以的,那父子类呢 ❓

    首先我们得知道父子类为啥要支持赋值呢,究其原因是它们之间存在一些强关联的关系,子类几乎包含父类的成员。

  • 派生类对象可以赋值给基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。这是在 public 继承的前提,因为如果是 public 继承,那么继承下来的成员的访问限定符是不变的;如果是 protected 或 private 继承,那么继承下来的成员的访问限定符可能会改变,进而导致不支持子类对父类赋值,因为可能存在类型转换。

    在这里插入图片描述

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

  • 但是基类的指针可以通过强制类型转换赋值给派生类的指针。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用 RTTI(Run-Time Type Information) 的 dynamic_cast 来进行识别后进行安全转换。(ps:这个后面会谈,这里先了解一下)。

  • 这就完了 ?当然不是,这里是需要和下面的派生类的默认成员函数一起理解。

二、继承中的作用域

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

class Person
{
protected:
	string _name = "dancebit";
	int _num = 111;
};

class Student : public Person
{
public:
	void Print()
	{
		cout << "姓名: " << _name << endl;
		cout << _num << endl;
		cout << Person::_num << endl;
	}
protected:
	int _num = 999;
};

class A
{
public:
	void fun()
	{
		cout << "fun()" << endl;
	}
};

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

void Test1()
{
	Student s;
	s.Print();
}

void Test2()
{
	B b;
	b.fun(10);
	//b.fun();
	b.A::fun();
}

int main()
{
	//Test1();
	Test2();

	return 0;
}
  • 在继承体系中基类和派生类都有独立的作用域,这意味着可以定义同名的成员,就像 STL 中 list 有 push_back,vector 也有 push_back。

    同时也就意味着现在有一个矛盾点是 Student 里有 2 个 _num,我们在访问 _num 时是访问基类的还是派生类的 ❓

      根据我们之前知道的全局变量和局部变量相同,局部优先的特性,我们可以猜测是这里是派生类优先。它们都有一个特性,那就是优先在自己的作用域查找。

  • Test1() 中,当派生类和基类有同名成员变量时,派生类成员变量会屏蔽基类成员变量,以此不能直接访问基类成员变量。这种情况叫做隐藏或重定义。如果想访问基类成员变量需要指定基类成员变量所在的作用域。

  • Test2() 中,当派生类和基类有同名成员函数 fun,但是参数不同时,它们之间存在什么关系 ❓

      首先 A 类的 fun 和 B 类的 fun 一定不构成函数重载,因为以前说过函数重载必须是在同一作用域,而我们刚说基类和派生类是不同的作用域。

      这里规定对于成员函数,构成隐藏的关系只需要函数名相同即可,而不用关心参数、返回值。所以这里 A 类的 fun 和 B 类的 fun 构成的就是隐藏,如果想访问基类中的 fun 需要指定其所在作用域。

  • 注意在实际中继承体系里面最好不要定义同名的成员变量和函数。这里其实也是 C++ 在设计时不好和复杂的地方,但是你也不能说如果同名就报错。就像北京有一个叫张三的,贵州也有一个叫张三的,这当然没有问题;但是同一个家庭不能大哥叫张三,二哥也叫张三,因为你要访问张三,你就要指定一个规则,如默认张三就是大哥、小张三就是二哥。C++ 中不能完全禁止同名的成员,因为一定会存在同名的隐藏关系,本章以及多态会碰到这样的场景。