目录
1.继承的概念及定义
1.1继承的概念
1.1.1 示例
#include<iostream>
#include<string>
using std::cout;
using std::cin;
using std::endl;
//基类Person
class Person
{
public:
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
std::string _name = "zsd"; // 姓名
int _age = 22; // 年龄
};
//继承后父类的Person的成员(成员函数和成员变量)都会变成子类的一部分。这里体现出了Student和
//Teacher复用了Person的成员。通过监视窗口查看Student和Teacher对象,可以看到变量的复用。
//调用Print可以看到成员函数的复用。
//派生类Student
class Student : public Person
{
protected:
int _stuid=10; // 学号(赋缺省值)
};
//派生类Teacher
class Teacher : public Person
{
protected:
int _jobid=20; // 工号
};
int main()
{
Student s;
Teacher t;
s.Print();
t.Print();
return 0;
}
1.2 继承基类成员访问方式的变化
1.2.1 权限不能放大,只能缩小
1.2.2 总结:
class Person
{
public:
void Print()
{
cout << _name << endl;
cout << _age << endl;
}
protected:
std::string _name="zhangsan"; // 姓名
private:
int _age=18; // 年龄
};
//class Student : protected Person
//class Student : private Person
class Student : public Person
{
protected:
int _stunum=2019; // 学号
};
int main()
{
Student s;
s.Print();
}
2.基类和派生类对象赋值转换
2.1 概念
(1)派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。
(2)基类的指针可以通过强制类型转换赋值给派生类的指针。但是必须是基类的指针是指向派生类对象时才是安全的。
2.2 示例
class Person
{
protected:
std::string _name; // 姓名
std::string _sex; // 性别
int _age=18; // 年龄
};
class Student : public Person
{
public:
int _No=2019; // 学号
};
void Test()
{
Student sobj;
// 1.子类对象可以赋值给父类对象/指针/引用
Person pobj = sobj;
Person* pp = &sobj;
Person& rp = sobj;
//2.基类对象不能赋值给派生类对象
//sobj = pobj;
// 3.基类的指针可以通过强制类型转换赋值给派生类的指针
pp = &sobj;
Student* ps1 = (Student*)pp; // 这种情况转换时可以的。
ps1->_No = 10;
//会报错:Run-Time Check Failure #2 - Stack around the variable 'pobj' was corrupted.
//pp = &pobj;
//Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问题
//ps2->_No = 10;
//cout << ps2->_No << endl;
}
int main()
{
Test();
return 0;
}
3.继承中的作用域
3.1 概念
3.2 示例
// 成员函数只要函数名相同就构成隐藏(重定义)
// Student的_num和Person的_num构成隐藏关系,不推荐这样用,容易混淆
class Person
{
public:
void fun()
{
cout << "Per::func()" << endl;
}
protected:
std::string _name = "zsd"; // 姓名
int _num = 111; // 身份证号
};
class Student : public Person
{
public:
void Print()
{
cout << " 姓名:" << _name << endl;
cout << " 显示访问:" << Person::_num << endl; //使用 作用域限定符 显示访问
cout << " 构成隐藏:" << _num << endl;
}
void fun()
{
cout << "Stu:func()" << endl;
}
protected:
int _num = 999; // 学号
};
void Test()
{
Student s1;
s1.Print();
s1.fun();
};
int main()
{
Test();
return 0;
}
4.派生类的默认成员函数
1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
3. 派生类的operator=必须要调用基类的operator=完成基类的复制。
4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
5. 派生类对象初始化先调用基类构造再调派生类构造。
6. 派生类对象析构清理先调用派生类析构再调基类的析构。
5.继承与友元
友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员
6.继承与静态成员
7.复杂的菱形继承及菱形虚拟继承
7.1 单继承
一个子类只有一个直接父类时称这个继承关系为单继承
//单继承
class A
{
};
class B : public A
{
};
class C : public B
{
};
7.2 多继承
一个子类有两个或以上直接父类时称这个继承关系为多继承
class A
{
};
class B
{
};
//多继承
class C : public A ,public B
{
};
7.3 菱形继承
7.3.1 菱形继承:菱形继承是多继承的一种特殊情况。
class A
{
public:
int _a=10;
};
class B : public A
{
public:
int _b=20;
};
class C : public A
{
public:
int _c=30;
};
//菱形继承
class D : public B, public C
{
public:
int _d=40;
};
int main()
{
D d;
// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
return 0;
}
7.3.2 菱形继承的问题:从数据冗余和二义性的问题。d中会有两份_a ,造成数据冗余和二义性
// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
cout << d.B::i << endl;
cout << d.C::i << endl;
7.4 菱形继承问题的解决办法
7.4.1 虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在B和C的继承 A 时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地方去使用。
class A
{
public:
int _a=10;
};
class B : virtual public A
{
public:
int _b=20;
};
class C : virtual public A
{
public:
int _c=30;
};
//菱形继承
class D : public B, public C
{
public:
int _d=40;
};
int main()
{
D d;
// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
return 0;
}
7.5 虚拟继承解决数据冗余和二义性的原理
8.继承和组合
class A
{
public:
int _a;
};
//对象组合
class B
{
public:
int _b;
A _f;
};