一、继承
1.继承的概念和意义
继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许我们在保持原有
类特性的基础上进行扩展,产生新的类,称子类。
例如下面的Student 类和Teacher类,它们都有人名,性别,电话,年龄,住址等信息。不同的是Student类有学号之分,而老师有职称之分。由此我们可以先定义一个Person 类,后面再让Student类和Teacher类继承Person类 从而达到代码复用的效果。
struct Person
{
public:
// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证
void identity()
{
cout << "void identity()" << _name << endl;
}
protected:
string _name = "张三";
// 姓名
int _age = 18;
// 年龄
string _address;
// 地址
string _tel;
//电话
};
struct Student: public Person
{
public:
// 学习
void study()
{
// ...
}
protected:
int _stuid;
//学号
};
class Teacher : public Person
{
public:
// 授课
void teaching()
{
//...
}
protected:
string title;
// 职称
};
2.定义格式
下面我们看到Person是父类,也称作基类。Student是子类,也称作派生类。
3.继承父类成员访问方式变化
(1)首先就是父类的private成员不管子类以何种方式继承都不可见的。不可见不是说没有继承到,只是说不管在子类的类外还是类内都不可以访问。
(2)剩余的继承方式总结一句就是父类和子类的访问限定符中取访问权限更小的。而public > protected > private。
(3) 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
(4)要注意就是一般来不会用private限定继承方式因为代码的可维护性会不太好。
4.继承类模板
#include<iostream>
#include<vector>
using namespace std;
namespace bit
{
// stack和vector的关系,既符合is-a,也符合has-a
template<class T>
class stack : public std::vector<T>
{
public:
void push(const T& x)
{
// ⽗类是类模板时,需要指定⼀下类域,
// 否则编译报错:error C3861: “push_back”: 找不到标识符
// 因为stack<int>实例化时,也实例化vector<int>了
// 但是模版是按需实例化,push_back等成员函数未实例化,所以找不到
vector<T>::push_back(x);
//push_back(x);
}
void pop()
{
vector<T>::pop_back();
}
const T& top()
{
return vector<T>::back();
}
bool empty()
{
return vector<T>::empty();
}
};
}
int main()
{
bit::stack<int> st;
st.push(1);
st.push(2);
st.push(3);
while (!st.empty())
{
cout << st.top() << " ";
st.pop();
}
return 0;
}
二、父类和子类对象赋值兼容转换
public继承的子类对象可以赋值给父类的对象/父类的指针/父类的引用。这里有形象的说法叫切片或切割,意思就是把子类中父类的那部分切来赋值给父类对象。
但是父类对象不能赋值给子类对象。
class Person
{
protected :
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;
//2.⽗类对象不能赋值给⼦类对象,这⾥会编译报错
sobj = pobj;
return 0;
}
三、继承中的作用域
1.在继承体系中父类和子类都有独立作用域。
2.若子类和父类中有同名函数,子类将屏蔽父类对同函数的访问,这种情况叫隐藏。(在子类成员函数中可以用父类名:父类成员函数进行访问)。
3.注意函数名相同即构成隐藏,因在命名时尽量避免同名函数。
class Person
{
protected:
string _name = "⼩李⼦"; // 姓名
int _num = 111;
// ⾝份证号
};
class Student : public Person
{
public:
void Print()
{
cout << " 姓名:" << _name << endl;
cout << " ⾝份证号:" << Person::_num << endl;
cout << " 学号:" << _num << endl;
}
protected:
int _num = 999; // 学号
};
int main()
{
Student s1;
s1.Print();
return 0;
};
四、子类的默认成员函数
1.子类的构造函数初始化对象时,必须先调用父类的构造函数初始化父类的那一部分成员。如果父类没有默认的构造函数,则必须在子类构造函数的初始化列表时显示调用。
2.子类的拷贝构造函数必须调用父类的拷贝构造完成父类的拷贝初始化。
3.子类的operator=必须要调用父类的operator=完成父类的复制。需要注意的是子类的operator=隐藏了父类的operator=,所以显示调用父类的operator=需要指定父类作用域。
4.子类的析构函数会在被调用完成后自动调用父类的析构函数清理父类成员。因为这样才能保证子类对象先清理子类成员再清理父类成员的顺序。
5.子类对象初始化先调用父类构造再调子类构造。
6.子类对象析构清理先调用子类析构再调父类的析构。
五、继承与友元
友元关系不能继承,也就是说父类友元函数不能访问子类成员。
若想访问则在子类中也加入友元声明。
class Student;
class Person
{
public:
friend void Display(const Person& p, const Student& s);
protected:
string _name; // 姓名
};
class Student : public Person
{
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;
}
六、继承与静态成员
父类定义了static静态成员,则整个继承体系里面只有⼀个这样的成员。无论派生出多少个子类,都只有⼀个static成员实例。
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;
cout << &s._name << endl;
// 这⾥的运⾏结果可以看到静态成员_count的地址是⼀样的
// 说明⼦类和⽗类共⽤同⼀份静态成员
cout << &p._count << endl;
cout << &s._count << endl;
// 公有的情况下,⽗⼦类指定类域都可以访问静态成员
cout << Person::_count << endl;
cout << Student::_count << endl;
return 0;
}
七、多继承和菱形继承问题
1.继承模型
(1)单继承:一个子类只有⼀个直接父类时称这个继承关系为单继承。
(2)多继承:一个子类只有两个即以上的直接父类时称这个继承关系为多继承。
(3)菱形继承:菱形继承是多继承的⼀种特殊情况。菱形继承的问题,从下面的对象成员模型构造,可以看出菱形继承有数据冗余和⼆义性的问题,在Assistant的对象中Person成员会有两份。支持多继承就⼀定会有菱形继承,像Java就直接不支持多继承,规避掉了这⾥的问题,所以实践中我们也是不建议设计出菱形继承这样的模型的。
#include<iostream>
using namespace std;
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()
{
// 编译报错:error C2385: 对“_name”的访问不明确
Assistant a;
a._name = "peter";
// 需要显⽰指定访问哪个⽗类的成员可以解决⼆义性问题,但是数据冗余问题⽆法解决
a.Student::_name = "xxx";
a.Teacher::_name = "yyy";
return 0;
}
2.虚继承
#include<iostream>
using namespce std:
class Person
{
public:
string _name; // 姓名
};
// 使⽤虚继承Person类
class Student : virtual public Person
{
protected:
int _num; //学号
};
// 使⽤虚继承Person类
class Teacher : virtual public Person
{
protected:
int _id; // 职⼯编号
};
// 教授助理
class Assistant : public Student, public Teacher
{
protected:
string _majorCourse; // 主修课程
};
int main()
{
// 使⽤虚继承,可以解决数据冗余和⼆义性
Assistant a;
a._name = "peter";
return 0;
}
3.多继承中指针偏移问题
就如下面的代码,Derive类继承了Base1类和Base2类,当实例化Derive d 时,将它分别赋值给Base1类指针p1和Base2类指针p2,在定义一个Derive类指针p3,就会发现p1==p3。同时若先继承Base2类,则p2==p3。
class Base1 { public: int _b1; };
class Base2 { public: int _b2; };
class Derive : public Base1, public Base2 { public: int _d; };
int main()
{
Derive d;
Base1* p1 = &d;
Base2* p2 = &d;
Derive* p3 = &d;
return 0;
}
class Base1 { public: int _b1; };
class Base2 { public: int _b2; };
class Derive : public Base2, public Base1 { public: int _d; };
int main()
{
Derive d;
Base1* p1 = &d;
Base2* p2 = &d;
Derive* p3 = &d;
return 0;
}
八、继承与组合
1.public继承是⼀种is-a的关系。也就是说每个子类对象都是⼀个父类对象。
2.继承允许你根据父类的实现来定义子类的实现。这种通过生成子类的复用通常被称为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,父类的内部细节对子类可见
但继承⼀定程度破坏了父类的封装,父类的改变,对子类有很大的影响。子类和父类间的依赖关系
很强,耦合度高。
3.组合是⼀种has-a的关系。假设B组合了A,每个B对象中都有⼀个A对象。
4.对象组合是类继承之外的另⼀种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。 组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。
5.优先使用组合,而不是继承。实际尽量多去用组合,组合的耦合度低,代码维护性好。不过也不太那么绝对,类之间的关系就适合继承(is-a)那就用继承,另外要实现多态,也必须要继承。类之间的关系既适合用继承(is-a)也适合组合(has-a),就用组合。
下面是本章节的思维导图
标签:继承,子类,C++,class,Person,父类,public From: https://blog.csdn.net/2401_86239408/article/details/142107066