1. 继承的概念
- 子类可以继承基类, 基类去派生子类
- Human 是基类,Student和Teacher是子类
2. 继承的语法
- 一个子类可以继承 多个基类,下面的代码都是只有一个基类。
#include <iostream>
using namespace std;
class Human {// 人类(基类)
public:
// 常引用才能引用右值
Human(const string& name, int age):m_name(name), m_age(age){};
void eat(const string& food){
cout << "我在吃" << food << endl;
}
void sleep(int hour){
cout << "我睡了" << hour << "个小时" << endl;
}
// protected属性下,在类的外部依然不能访问, 但是子类能访问基类这些成员变量
// private属性下类的外部和子类都不能访问
protected:
string m_name;
int m_age;
};
class Student:public Human{// 学生类(人类派生的一个子类)
public:
Student(const string& name, int age, int no):Human(name, age), m_no(no){};
void learn(const string& course){
cout << "我在学" << course << endl;
}
void who(void){
cout << "我叫"<< m_name << ",今年" << m_age << "岁,"<< "学号是 " << m_no << endl;
}
private:
int m_no;
};
class Teacher:public Human{// 教师类(人类派生的一个子类)
public:
Teacher(const string& name, int age, int salary):Human(name, age), m_salary(salary){}
void teach(const string& course){
cout << "我在教" << course << endl;
}
void who(void){
cout << "我叫"<< m_name << ",今年" << m_age << "岁,"<< "薪水是 " << m_salary << endl;
}
private:
int m_salary;
};
int main(void){
Student s("yue han", 18, 666);
s.who();
s.eat("炸酱面");
s.sleep(8);
s.learn("c++");
Teacher t("jie ke", 30, 20000);
t.who();
t.eat("油泼面");
t.sleep(9);
t.teach("c++");
return 0;
}
$ ./a.out
我叫yue han,今年18岁,学号是 666
我在吃炸酱面
我睡了8个小时
我在学c++
我叫jie ke,今年30岁,薪水是 20000
我在吃油泼面
我睡了9个小时
我在教c++
3. 公有继承的特性
3.1 任何时候都可以把一个子类对象看作是是一个基类对象
3.2 向上造型、向下造型 (基类在上,子类在下)
- 向上造型,通俗点讲就是,一个基类指针可以指向任何一个子类对象,但是只能访问子类对象中的基类子对象。
- 向上造型的产物:指向子类对象的基类指针,引用用子类对象的基类引用。
- 向下造型,要注意使用的合理性。
#include <iostream>
using namespace std;
class Human {// 人类(基类)
public:
// 常引用才能引用右值
Human(const string& name, int age):m_name(name), m_age(age){};
void eat(const string& food){
cout << "我在吃" << food << endl;
}
void sleep(int hour){
cout << "我睡了" << hour << "个小时" << endl;
}
// protected属性下,在类的外部依然不能访问, 但是子类能访问基类这些成员变量
// private属性下类的外部和子类都不能访问
protected:
string m_name;
int m_age;
};
class Student:public Human{// 学生类(人类派生的一个子类)
public:
Student(const string& name, int age, int no):Human(name, age), m_no(no){};
void learn(const string& course){
cout << "我在学" << course << endl;
}
void who(void){
cout << "我叫"<< m_name << ",今年" << m_age << "岁,"<< "学号是 " << m_no << endl;
}
private:
int m_no;
};
int main(void){
Student s("yue han", 18, 666);
s.who();
s.eat("炸酱面");
s.sleep(8);
s.learn("c++");
// Student* ==> Human*: 向上造型
Human* ph = &s;
ph->eat("苹果");
ph->sleep(8);
// ph->who(); // 编译Error
// Human* ==> Student: 向下造型 (合理,因为ph本来就是指向的Student类对象的指针)
// Student* = ph; // 编译Error
Student* ps = static_cast<Student*>(ph);
ps->who();
// Human* ==> Student: 向下造型 (不合理)
// Human创建的对象, 对应在内存里的数据只有m_name, m_age,
Human h("li ming", 18);
// 通过向下造型得到的Student对象,是可以访问m_no的,但h对应内存中没有m_no的数据
Student* ps2 = static_cast<Student*>(&h);
// 所以通过 ps2 直接访问 m_no 是有异常的
ps2->who();
return 0;
}
$ ./a.out
我叫yue han,今年18岁,学号是 666
我在吃炸酱面
我睡了8个小时
我在学c++
我在吃苹果
我睡了8个小时
我叫yue han,今年18岁,学号是 666
我叫li ming,今年18岁,学号是 0
3.3 子类 继承/隐藏 基类成员
- 虽然子类和基类定义同名的函数,因为作用域不同,不会构成重载关系,但是可以在子类中 通过using 基类名:函数名, 把基类当中的函数声明到当前作用域,这样就可以构成函数重载关系了。但它并不具备通用性。
- 推荐直接用 类名:: 显示指明
#include <iostream>
using namespace std;
class Base{
public:
void func(void){
cout << "Base:func(void)" << endl;
}
};
class Derived:public Base{
public:
void func(int i){
cout << "Derived:func(int)" << endl;
}
using Base::func; //但是不具备通用型,因为如果两个函数参数也一样,就没法构成重载关系
};
int main(void){
Derived d;
d.func(4); // Derived:func(int)
// 子类中 有 using Base::func; 子类中的func(int)和 基类中的func(void)才能构成函数重载
d.func(); // Base:func(void)
d.Base::func(); // Base:func(void)
return 0;
}
4. 继承方式和访问控制属性
4.1 访问控制属性 (public、protected、private)
4.2 继承方式 (public、protected、private)
- 继承方式并不影响在子类内部对基类成员 原本 的 可访问性。
- 不同的继承方式实质上影响的是 通过 子类 访问 基类中成员时 的 可访问性
- 1. 通过子类对象在类的外部访问基类成员
- 2. 在子类的子类中访问基类成员
#include <iostream>
using namespace std;
class A{// 基类
public:
int m_public;
protected:
int m_protected;
private:
int m_private;
};
class B:public A{// 公有继承的子类
};
class C:protected A{// 保护继承的子类
};
class D:private A{// 私有继承的子类
};
// 2. 通过子类的子类访问基类成员
class X:public B{
void func(void){
m_public; // 对基类成员的访问控制属性 public
m_protected; // protected
// m_private; // Error, private
}
};
class Y:public C{
void func(void){
m_public; // protected
m_protected; // protected
// m_private; // Error, private
}
};
class Z:public D{
void func(void){
// m_public; // Error, private
// m_protected; // Error, private
// m_private; // Error, private
}
};
int main(void){
// 1. 通过子类对象在类的外部访问基类成员
B b;
b.m_public; // 对基类成员的访问控制属性: public
// b.m_protected;// Error, protected
// b.m_private; // Error, private
C c;
// c.m_public; // Error, protected
// c.m_protected;// Error, protected
// c.m_private; // Error, private
D d;
// d.m_public; // Error, private
// d.m_protected;// Error, private
// d.m_private; // Error, private
return 0;
}
4.3 向上造型在 私有/保护继承中不在适用
#include <iostream>
using namespace std;
class Base{
public:
int m_data;
};
class Derived_public:public Base{};
class Derived_private:private Base{};
int main(void){
Derived_public d1;
Base* pb1 = &d1;
pb1->m_data;
// 向上造型在 私有/保护继承中不在适用
// 通过d2对基类成员m_data的访问控制属性应该是private, 相当于访问属性变小
Derived_private d2;
// 这时再向上造型,转为Base基类类型,好像又可以访问m_data了,相当于访问属性变大,这时很矛盾的,不被允许的
// Base* pb2 = &d2; // Error
return 0;
}
5. 子类的的构造和析构
5.1 子类的构造
5.2 子类的析构
#include <iostream>
using namespace std;
class Base{
public:
Base(void):m_data(0){
cout << "Base 缺省构造" << endl;
}
Base(int i):m_data(i){
cout << "Base 有参构造" << endl;
}
~Base(void){
cout << "Base 析构" << endl;
}
int m_data;
};
class Member{
public:
Member(void):m_i(0){
cout << "Member 缺省构造" << endl;
}
Member(int i):m_i(i){
cout << "Member 有参构造" << endl;
}
~Member(void){
cout << "Member 析构" << endl;
}
int m_i;
};
class Derived:public Base{
public:
// 如果没有显示指明,将调用基类的的无参构造函数来初始化基类子对象
Derived(void){
cout << "Derived 缺省构造" << endl;
}
// Base(i) 显示指明基类子对象的初始化方式
// m_mem(i) 显示指明成员子对象的初始化方式
Derived(int i):Base(i), m_mem(i){
cout << "Derived 有参构造" << endl;
}
~Derived(void){
cout << "Derived 析构" << endl;
}
Member m_mem;
};
int main(void){
Derived d1;
Derived d2(100);
return 0;
}
$ ./a.out
Base 缺省构造
Member 缺省构造
Derived 缺省构造
Base 有参构造
Member 有参构造
Derived 有参构造
# 析构和构造的过程对称相反
Derived 析构
Member 析构
Base 析构
Derived 析构
Member 析构
Base 析构
5.3 一个内存泄露的风险
int main(void){
// 注意下面的代码有内存泄露风险
// 在堆区创建一个对象(1.malloc分配内存,2.调Derived的构造函数),然后向上造型
Base* pb = new Derived;
// 销毁堆区的对象(1.调基类Base的析构函数(因为pb是基类指针) 2.free内存)
delete pb;
// 上面只是调用了基类的析构函数,只是会把基类中维护的动态资源释放掉。
// 没有调子类和成员对象的析构函数。而子类和成员对象当中的动态资源是没有被释放的。
// 这将造成内存泄露
pb = NULL;
return 0;
}
$ ./a.out
Base 缺省构造
Member 缺省构造
Derived 缺省构造
Base 析构
- 这个风险可以用虚析构函数来解决掉