类继承
《C++ Primer Plus》笔记
类继承语法
在类名后面使用冒号表示继承关系
// 父类(基类)
class Base {
...
};
// 子类(派生类)
class Derived : Base {
...
};
派生类的构造方法必须使用基类的构造方法
若未调用,则会被加上默认构造方法(若没有默认无参构造方法,则报错),下面两个等价
Derived::Derived() {
...
}
Derived::Derived() : Base() {
...
};
public, protected和private
派生类不能访问基类的private成员和方法,但是能访问public和private成员和方法
外部不能访问private和protected成员和方法,只能访问public成员和方法
protected对下开放,对外关闭
// 父类(基类)
class Base {
public:
string b_str1;
void b_fun1();
protected:
string b_str2;
void b_fun2();
private:
string b_str3;
void b_fun3();
};
// 子类(派生类)
class Derived : Base {
public:
void d_fun();
};
Derived::d_fun() {
cout << b_str1 << endl; // 能够访问
cout << b_str2 << endl; // 能够访问
cout << b_str3 << endl; // 报错, 不能访问
b_fun1(); // 能够访问
b_fun2(); // 能够访问
b_fun3(); // 报错, 不能访问
}
fun() {
Base b;
cout << b.b_str1 << endl; // 能够访问
cout << b.b_str2 << endl; // 报错, 不能访问
cout << b.b_str3 << endl; // 报错, 不能访问
b.b_fun1(); // 能够访问
b.b_fun2(); // 报错, 不能访问
b.b_fun3(); // 报错, 不能访问
}
多态
基类类型的指针和引用可以指向派生类的对象
Base* b = new Derived;
Derived derived;
Base& b = derived;
派生类能够定义基类中相同名称和参数的方法
派生类定义了相同方法时,基类的所有同名方法都将被隐藏,包括不同参数和返回值的同名方法
// 父类(基类)
class Base {
public:
void fun() {
cout << "Base fun" << endl;
}
};
// 子类(派生类)
class Derived : Base {
void fun() { // 定义基类中相同的方法
cout << "Derived fun" << endl;
}
};
使用基类指针或引用时,调用的时基类的对应方法
使用派生类指针或引用时,调用派生类的方法
Derived derived;
derived.fun(); // 输出Derived fun
Base& b = derived;
b.fun(); // 输出Base fun
Base* b2 = &derived;
b2->fun(); // 输出Base fun
虚函数
在基类方法前面加上virtual关键字,将一直调用派生类方法
// 父类(基类)
class Base {
public:
virtual void fun() {
cout << "Base fun" << endl;
}
};
// 子类(派生类)
class Derived : Base {
void fun() { // 定义基类中相同的方法
cout << "Derived fun" << endl;
}
};
Derived derived;
derived.fun(); // 输出Derived fun
Base& b = derived;
b.fun(); // 输出Derived fun
Base* b2 = &derived;
b2->fun(); // 输出Derived fun
抽象类
虚函数可以不被实现, 被称作纯虚函数
纯虚函数对应的类被称作抽象类,不能生成实例化对象
class Base {
public:
virtual void fun() = 0;
};
Base base; // 报错
静态联编和动态联编
虚函数将使用虚函数指针,指向虚函数表对应的地址,所以只有在运行时才能选择正确的方法, 被称为动态联编
普通方法在编译时期就能确定对应的函数是哪个,这杯成为静态联编
使用虚函数,存在以下问题:
- 对于每个类,都需要创建虚函数指针和虚函数表,每个对象的存储空间都更大
- 对于每个函数调用,都存在多余的寻址操作
虚函数与析构函数
析构函数最好为虚函数,当使用基类指针或引用指向派生类对象时,将不会调用派生类析构函数,无法释放派生类new操作的内存
// 父类(基类)
class Base {
...
};
// 子类(派生类)
class Derived : Base {
private:
char* str;
public:
Derived() {
str = new char[10];
...
}
~Derived() {
delete[] str;
...
}
};
Base* b2 = new Derived;
delete b2; // 不会调用派生类析构函数,str内存无法释放
继承类型
继承同样存在public,protected,private三种类型,继承是使用相对应的关键字
// 父类(基类)
class Base {
public:
void fun() {
cout << "Base fun" << endl;
}
};
// 子类1(派生类)
class Derived1 : public Base {
void fun() { // 定义基类中相同的方法
cout << "Derived fun" << endl;
}
};
// 子类2(派生类)
class Derived1 : protected Base {
void fun() { // 定义基类中相同的方法
cout << "Derived fun" << endl;
}
};
// 子类3(派生类)
class Derived1 : private Base {
void fun() { // 定义基类中相同的方法
cout << "Derived fun" << endl;
}
};
特征 | 公有继承 | 保护继承 | 私有继承 |
---|---|---|---|
公有成员变成 | 公有成员 | 保护成员 | 私有成员 |
保护成员变成 | 保护成员 | 保护成员 | 私有成员 |
私有成员变成 | 无法访问 | 无法访问 | 无法访问 |
能否隐式向上转换 | 是 | 是,只能在派生类中 | 否 |
虚基类
多重继承
派生类可以同时继承多个基类
// 父类(基类)
class Base1 {
...
};
class Base2 {
...
};
// 子类(派生类)
class Derived : Base1, Base2 {
...
};
菱形继承问题
当一个类继承多个基类,而这些基类又同时是某一个类的派生类时,该类内部有多个基类对象,若调用基类的相同名称的方法,将会产生二义性
可以使用作用域解析来避免二义性
// 父类(基类)
class Base {
public:
fun();
};
class Base1 : Base {
...
};
class Base2 : Base {
...
};
// 子类(派生类)
class Derived : Base1, Base2 {
...
};
Derived derived;
// derived.fun(); 产生二义性,不知道调用来自Base1的方法还是Base2的方法
derived.Base1::fun();
derived.Base2::fun();
虚基类使用virtual关键字,使得派生类只产生一个基类对象副本
// 父类(基类)
class Base {
public:
fun();
};
class Base1 : virtural public Base {
...
};
class Base2 : public virtural Base {
.;..
}
// 子类(派生类)
class Derived : Base1, Base2 {
...
};
Derived derived;
derived.fun(); // 不再产生二义性
普通继承中,构造方法不能调用基类的基类的构造方法, 而在虚基类继承中,必须调用基类的基类的构造方法,若没有,则调用默认构造方法
class Base {
public:
int m_a;
Base(int a) : m_a(a) {};
};
class Base1 : public Base {
public:
int m_b;
Base1(int a, int b) : Base(a), m_b(b) {};
};
class Base2 : public Base {
public:
int m_c;
Base2(int a, int c) : Base(a), m_c(c) {};
};
// 子类(派生类)
class Derived : Base1, Base2 {
Derived(int a, int b, int c) : Base1(a, b), Base2(a, c) {};
// Derived(int a, int b, int c) : Base(a), Base1(a, b), Base2(a, c) {}; 非法
};
class Base {
public:
int m_a;
Base(int a) : m_a(a) {};
};
class Base1 : virtual public Base {
public:
int m_b;
Base1(int a, int b) : Base(a), m_b(b) {};
};
class Base2 : virtual public Base {
public:
int m_c;
Base2(int a, int c) : Base(a), m_c(c) {};
};
// 子类(派生类)
class Derived : Base1, Base2 {
// 必须调用Base构造方法
Derived(int a, int b, int c) : Base(a), Base1(a, b), Base2(a, c) {};
};