目录
1.继承的概念
继承是面向对象编程中的一个核心概念,它允许一个类(称为子类或派生类)基于另一个类(称为父类或基类)来构建,从而共享父类的属性和方法,同时可以扩展或修改其功能。
继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的函数层次的复用(模版),继承是类设计层次的复用。
2.继承的格式
3.继承基类成员访问方式
使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
在实际运用中,我们一般是选择public继承,很少使用protected/private继承。
4.继承类模版
在前面这篇博客中我们底层实现stack和queue的时候,就相当于是把deque当成基类/父类。
相当于下面这段代码实现:
namespace yfr
{
template<class T>
class stack : public std::deque<T>
{
public:
void push(const T& x)
{
// 基类是类模板时,需要指定类域,
// 否则编译报错:error C3861: “push_back”: 找不到标识符
// 因为stack<int>实例化时,也实例化deque<int>了
// 但是模版是按需实例化,push_back等成员函数未实例化,所以找不到
deque<T>::push_back(x);
//push_back(x);
}
void pop()
{
deque<T>::pop_back();
}
const T& top()
{
return deque<T>::back();
}
bool empty()
{
return deque<T>::empty();
}
};
}
int main()
{
yfr::stack<int> st;
st.push(1);
st.push(2);
st.push(3);
while (!st.empty())
{
cout << st.top() << " ";
st.pop();
}
// 3 2 1
return 0;
}
5.基类和派生类的转换
1. public继承的派生类对象可以赋值给基类的指针/基类的引用。引用的是派生类中基类的部分,所以这个过程也有个形象化的叫法:切片或者切割。
2. 基类对象不能赋值给派生类对象。
3. 基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。
class person
{
public:
void print()
{
cout << "我是" << _name << endl;
cout << "我的年龄是:" << _age << endl;
cout << "我的身高是:" << _height << endl;
cout << "我的性别是:" << _sex << endl;
cout << endl;
}
protected:
string _name="人类";
int _age= 100;
float _height=0.00;
string _sex = "未知";
};
class student:public person
{
public:
student()
{
_name = "学生";
_age = 18;
_height = 175.8;
_sex = "男/女";
_stuid = "2222222";
}
void print()
{
cout << "我是" << _name << endl;
cout << "我的年龄是:" << _age << endl;
cout << "我的身高是:" << _height << endl;
cout << "我的性别是:" << _sex << endl;
cout <<"我的ID:" << _stuid << endl;
cout << endl;
}
protected:
string _stuid;
};
class teacher :public person
{
public:
teacher()
{
_name = "老师";
_age = 38;
_height = 185.3;
_sex = "男/女";
_tchid = "1111111";
}
void print()
{
cout << "我是" << _name << endl;
cout << "我的年龄是:" << _age << endl;
cout << "我的身高是:" << _height << endl;
cout << "我的性别是:" << _sex << endl;
cout << "我的ID:" << _tchid << endl;
cout << endl;
}
protected:
string _tchid;
};
测试运行:
6.继承的作用域
1. 在继承体系中基类和派生类都有独立的作用域。
2. 派生类和基类中有同名成员,派生类成员将屏蔽基类对同名成员的直接访问,这种情况叫隐藏。(在派生类成员函数中,可以使用 基类::基类成员 显示访问)
3. 成员函数的隐藏,只需要函数名相同就构成隐藏。
4. 注意在实际中在继承体系里面最好不要定义同名的成员。
注:隐藏常和函数重载区别:重载(必须在同一作用域,且函数参数不同);隐藏(子类和父类成员名字相同时,父类函数隐藏,不允许直接访问)
class Person
{
protected:
string _name = "人类";
int _age;
};
class Student:public Person
{
public:
Student()
{
_name = "学生";
}
void print()
{
cout << _name << endl;
cout << person::_name << endl;
}
protected:
string _name;
};
测试运行:
7.派生类的默认成员函数
1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
2. 派生类对象初始化先调用基类构造再调派生类构造。
3.派生类对象析构清理先调用派生类析构再调基类的析构。
4. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
5.派生类的operator=必须要调用基类的operator=完成基类的复制。注意:派生类的operator=隐藏了基类的operator=,所以显示调用基类的operator=,需要指定基类作用域
8.继承与友元
友元函数不能被继承
//先声明
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;
Display(p, s);
return 0;
}
9.继承与静态成员
基类定义了static静态成员,则整个继承体系里面只有⼀个这样的成员。无论派生出多少个派生类,都只有⼀个static成员实例。
10.多继承问题
单继承:派生类只继承一个基类
多继承:派生类继承多个基类
菱形继承:一种特殊的多继承情况,形成菱形结构的继承关系。会导致数据冗余和二义性,解决方法:虚继承(在中间类继承时加一个关键字virtual)
代码实现:
class Base {
public:
int data;
};
//class Derived1 : public Base {};
//class Derived2 : public Base {};
//虚继承
class Derived1 : virtual public Base {};
class Derived2 : virtual public Base {};
class MostDerived : public Derived1, public Derived2 {}; // 菱形继承
int main() {
MostDerived obj;
obj.data = 10; // 错误:二义性,不知道是从Derived1还是Derived2继承的data。但是虚继承之后就不会报错了
}
11.继承与组合
继承是面向对象编程中"is-a"关系的实现方式,允许派生类(子类)获取基类(父类)的属性和方法。
组合是"has-a"关系的实现方式,表示一个类包含另一个类的对象作为其成员。
//轮胎
class Tire
{
public:
Tire()
{
cout << "Tire" << endl;
}
};
//车 车和轮胎是has a的关系,即组合关系
class Car
{
public:
Car()
{
Tire t1,t2;
cout << "Car" << endl;
}
};
int main()
{
Car car;
return 0;
}