① public继承的子类对象可以赋值给 父类的对象 / 父类的指针 / 父类的引用。这里有个形象的说法叫切片或者切割。寓意把子类中父类那部分切来赋值过去。 ② 父类对象不能赋值给子类对象。 ③ 父类的指针或者引用可以通过强制类型转换赋值给子类的指针或者引用。但是必须是父类的指针是指向子类对象时才是安全的。这里父类如果是多态类型,可以使用RTTI(Run-Time TypeInformation)的dynamic_cast 来进行识别后进行安全转换。(ps:类型转换再讲解)
cpp复制代码
class Person
{
public:
string _name; // 姓名
string _sex; // 性别
int _age; // 年龄
};
class Student : public Person
{
public:
int _No; // 学号
};
int main()
{
Student sobj;
// 1.派生类对象可以赋值给基类的赋值/指针/引用
Person pobj = sobj;//通过调用基类的拷贝构造完成的
Person* pp = &sobj;//指向子类中切出来的父类的部分
Person& rp = sobj;
rp._age = 10;//子类可以修改父类
//上述&不产生临时对象
int a = 0;
//double& b = a;
//error:产生了临时对象,而临时对象具有常性(这里引用的是临时对象),发生了权限放大,所以要用const修饰
const double& b = a;
//2.基类对象不能赋值给派生类对象,但是指针/引用强转后可以
//sobj = (Student)pobj;
Student* ps = (Student*)pp;
return 0;
}
① 子类的构造函数必须调用父类的构造函数初始化父类的那一部分成员。如果父类没有默认的构造函数,则必须在子类构造函数的初始化列表阶段显示调用。 ② 子类的拷贝构造函数必须调用父类的拷贝构造完成父类的拷贝初始化。 ③ 子类的operator=必须要调用父类的operator=完成父类的复制。需要注意的是子类的operator=隐藏了父类的operator=,所以显示调用父类的operator=,需要指定父类作用域 ④ 子类的析构函数会在被调用完成后自动调用父类的析构函数清理父类成员。因为这样才能保证子类对象先清理子类成员再清理父类成员的顺序。 ⑤ 子类对象初始化先调用父类构造再调子类构造。 ⑥ 子类对象析构清理先调用子类析构再调父类的析构。 ⑦ 因为多态中一些场景析构函数需要构成重写,重写的条件之一是函数名相同(这个我们多态章节会讲解)。那么编译器会对析构函数名进行特殊处理,处理成destructor(),所以父类析构函数不加virtual的情况下,子类析构函数和父类析构函数构成隐藏关系。
// C++11的方法
class Base final
{
public:
void func5() { cout << "Base::func5" << endl; }
protected:
int a = 1;
private:
// C++98的⽅法
/*Base()
{}*/
};
class Derive :public Base
{
void func4() { cout << "Derive::func4" << endl; }
protected:
int b = 2;
};
int main()
{
Base b;
Derive d;
return 0;
}
5.继承与友元
友元关系不能继承,也就是说父类友元不能访问子类私有和保护成员。
cpp复制代码
// 前置声明
class Student;
class Person
{
public:
friend void Display(const Person& p, const Student& s);
protected:
string _name;
};
class Student : public Person
{
public: // 友元关系不能继承,要想调用就得重新定义友元
friend void Display(const Person& p, const Student& s);
protected:
int _stuNum;
};
void Display(const Person& p, const Student& s)
{
cout << p._name << endl;
cout << s._stuNum << endl;
}
int main()
{
Person p;
Student s;
// 编译报错:error C2248: "Student::_stuNum": 无法访问 protected 成员
// 解决⽅案:Display也变成Student 的友元即可
Display(p, s);
return 0;
}
6.继承与静态成员
cpp复制代码
class Person
{
public:
string _name;
static int _count;
};
int Person::_count = 0;
class Student : public Person
{
protected:
int _stuNum;
};
int main()
{
Person p;
Student s;
// 这里的运行结果可以看到非静态成员_name的地址是不⼀样的
// 说明子类继承下来了,父子类对象各有⼀份
cout << &p._name << endl;//0000007E7AB6F748
cout << &s._name << endl;//0000007E7AB6F788
// 这里的运行结果可以看到静态成员_count的地址是⼀样的
// 说明子类和父类共用同一份静态成员
cout << &p._count << endl;//00007FF7D60E0440
cout << &s._count << endl;//00007FF7D60E0440
// 公有的情况下,父子类指定类域都可以访问静态成员
cout << Person::_count << endl;//0
cout << Student::_count << endl;//0
return 0;
}
7.多继承及其菱形继承问题
(1) 继承模型
① 单继承:一个子类只有一个直接父类时称这个继承关系为单继承 ② 多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承,多继承对象在派生类内存中的模型是,先继承的父类在前面,后面继承的父类在后面,子类成员在放到最后面。 ③ 菱形继承:菱形继承是多继承的一种特殊情况。菱形继承的问题,从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题,在Assistant的对象中Person成员会有两份。支持多继承就一定会有菱形继承,像Java就直接不支持多继承,规避掉了这里的问题,所以实践中我们也是不建议设计出菱形继承这样的模型的。
cpp复制代码
class Person
{
public:
string _name; // 姓名
};
class Student : public Person
{
protected:
int _num; //学号
};
class Teacher : public Person
{
protected:
int _id; // 职⼯编号
};
class Assistant : public Student, public Teacher
{
protected:
string _majorCourse; // 主修课程
};
int main()
{
Assistant a;
// 编译报错:error C2385: 对"_name"的访问不明确
a._name = "peter";
// 需要显⽰指定访问哪个⽗类的成员可以解决二义性问题,但是数据冗余问题⽆法解决
a.Student::_name = "xxx";
a.Teacher::_name = "yyy";
return 0;
}
template<class CharT, class Traits = std::char_traits<CharT>>
class basic_ostream : virtual public std::basic_ios<CharT, Traits>
{};
template<class CharT, class Traits = std::char_traits<CharT>>
class basic_istream : virtual public std::basic_ios<CharT, Traits>
{};
8.继承和组合
cpp复制代码
// Tire(轮胎)和Car(⻋)更符合has-a的关系
class Tire {
protected:
string _brand = "Michelin"; // 品牌
size_t _size = 17; // 尺⼨
};
class Car {
protected:
string _colour = "白"; // 颜⾊
string _num = "陕ABIT00"; // ⻋牌号
Tire _t1; // 轮胎
Tire _t2; // 轮胎
Tire _t3; // 轮胎
Tire _t4; // 轮胎
};
class BMW : public Car {
public:
void Drive() { cout << "好开-操控" << endl; }
};
// Car和BMW/Benz更符合is-a的关系
class Benz : public Car {
public:
void Drive() { cout << "好坐-舒适" << endl; }
};
template<class T>
class vector
{};
// stack和vector的关系,既符合is-a,也符合has-a
//继承
template<class T>
class stack : public vector<T>
{};
template < class T>
// 组合(推荐)
class stack
{
public:
vector<T> _v;
};
int main()
{
return 0;
}