携手创作,共同成长!这是我参与「掘金日新计划 · 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++ 中不能完全禁止同名的成员,因为一定会存在同名的隐藏关系,本章以及多态会碰到这样的场景。