继承
继承就是新类从已有类那里得到已有的特性。
类的派生指的是从已有类产生新类的过程。
原有的类成为基类或父类,产生的新类称为派生类或子类。
// 基类
class Animal {
// eat() 函数
// sleep() 函数
};
// 派生类
class Dog : public Animal {
// bark() 函数
};
如果不希望一个类被继承,也可以使用 final 关键字。
格式如下:
class Class_name final
{
...
};
则该类将不能被继承。
单继承/多继承
定义一个派生类,我们使用一个类派生列表来指定基类。类派生列表以一个或多个基类命名,形式如下:
// 单继承
class <派生类名> : <继承方式> <基类名> {
<派生类类体>
};
// 多继承
class <派生类名> : <继承方式1> <基类名1>, <继承方式2> <基类名2>, ... {
<派生类类体>
};
<继承方式1>即访问修饰符 access-specifier ,是 public、protected 或 private 之一,缺省默认为 private。
成员变量和成员函数不写访问修饰符时,默认也是 private。
多继承二义性例子
class A {
public:
void z() {
cout << "Az" << endl;
}
};
class B {
public:
void z() {
cout << "Bz" << endl;
}
};
class C: public A, public B {
};
int main() {
C c1;
//c1.z(); // [Error] request for member 'z' is ambiguous;candidates are: void B::z();void A::z()
// 通过指定成员名来限定域,消除二义性
c1.A::z();
c1.B::z();
return 0;
}
一个派生类继承了所有的基类方法,除了以下的方法:
- 基类的构造函数、析构函数和拷贝构造函数。
- 基类的重载运算符。
- 基类的友元函数。
转换与继承
-
派生类对象可以赋值或初始化基类对象。
-
基类对象不可以赋值给派生类对象。
-
基类对象的指针和引用可以指向派生类对象。
-
派生类对象的指针和引用不能指向基类对象,但是可以通过强制转化完成。
继承与静态成员
-
基类定义了static成员,则整个继承体系里面只有一个这样的成员。**无论派生出多少个子类,都只有一个static成员实例。**所有类对象共享一个static类成员,static类对象必须要在类外进行初始化。
-
static成员遵循常规访问控制与继承
继承与友元关系
- 友元关系不能被继承,即友元无法访问基类的私有成员与被保护成员
隐藏
在继承体系中基类和派生类都有独立的作用域。若在派生类和基类中有同名成员(不管是变量还是函数),基类将隐藏对父类的同名成员的访问,这种情况称为同名隐藏或者重定义。就是在派生类中使用该成员(包括在定义派生类时使用,也包括通过派生类对象访问该成员)时,实际上使用的是派生类新增的成员,而不是从基类继承来的。
隐藏例子
class A {
public:
const static int aa = 3;
void z() {
cout << "Az" << endl;
}
void x() {
cout << "Ax" << endl;
}
};
class C: public A {
public:
const static int aa = 5;
void z() {
cout << "Cz" << endl;
}
void x(int a) {
cout << "Cx" << endl;
}
};
int main() {
C c1;
cout << c1.aa << endl; // 5,C类隐藏了A类的同名成员变量aa
cout << c1.A::aa << endl; // 3,指定成员名获取被隐藏的成员变量
c1.z(); // Cz
c1.A::z(); // Az
c1.x(); // 编译失败,因为调用的是C类的方法x,即,只要同名,哪怕参数不同也能构成隐藏
return 0;
}
访问控制
访问方式 | public | protected | private |
---|---|---|---|
同一个类和友元类内部 | 可以 | 可以 | 可以 |
派生类内部 | 可以 | 可以 | 不可以 |
外部类 | 可以 | 不可以 | 不可以 |
继承方式 | public继承 | protected继承 | private继承 |
---|---|---|---|
基类的public成员 | 仍为public成员 | 变为protected成员 | 变为private成员 |
基类的protected成员 | 仍为protected成员 | 仍为protected成员 | 变为private成员 |
基类的private成员 | 不可见 | 不可见 | 不可见 |
外部类其实就是指能不能通过对象来访问。所以只有public的成员可以通过对象来访问。
继承方式中的 public、protected、private 是用来指明基类成员在派生类中的最高访问权限的。
**基类的 private 成员不能在派生类中使用,不代表基类的 private 成员不能被继承。**实际上,基类的 private 成员是能够被继承的,并且(成员变量)会占用派生类对象的内存,它只是在派生类中不可见,导致无法使用罢了。
访问控制例子
class Animal {
public:
void eat() {
cout << "animal eat" << endl;
}
protected:
void sleep() {
cout << "animal sleep" << endl;
}
private:
void breathe() {
cout << "animal breathe" << endl;
}
};
class Fish : public Animal {
public:
void test() {
eat(); // 此时eat()的访问权限为public,派生类内部能够访问
sleep(); // 此时sleep()的访问权限为protected,派生类内部能够访问
breathe(); // 此时breathe()的访问权限为no access,派生类内部不能够访问
}
};
int main(void) {
Fish f;
f.eat(); // 此时eat()的访问权限为public,在类外部能够访问
f.sleep(); // 此时sleep()的访问权限为protected,在类外部不能够访问
f.breathe(); // 此时breathe()的访问权限为no access,在类外部不能够访问
return 0;
}
修改访问权限
使用 using 关键字可以改变基类成员在派生类中的访问权限,例如将 public 改为 private、将 protected 改为 public。
注意:using 只能改变基类中 public 和 protected 成员的访问权限,不能改变 private 成员的访问权限,因为基类中 private 成员在派生类中是不可见的,根本不能使用,所以基类中的 private 成员在派生类中无论如何都不能访问。
class A {
public:
void z() {
cout << "Az" << endl;
}
protected:
int a1;
int b1;
};
class C: public A {
public:
using A::a1; // 将a1的访问权限改为public
private:
using A::z; // 将z的访问权限改为private
};
int main() {
A a1;
a1.z();
C c1;
c1.a1 = 6;
cout << c1.a1 << endl; // 6,修改成public成功,类外可以访问
//c1.b1 = 6; // 设置失败,是protected权限
//c1.z(); // 设置失败,是private权限
return 0;
}
环状继承/菱形继承
class D{......};
class B: public D{......};
class A: public D{......};
class C: public B, public A{.....};
这个继承会使D创建两个对象,虽然不会报错但是会造成二义性,要解决就要用虚继承
格式:class 类名: virtual 继承方式 父类名
虚继承在创建对象的时候会创建一个虚表
class D{......};
class B: virtual public D{......};
class A: virtual public D{......};
class C: public B, public A{.....};
菱形继承例子
class D {
public:
D() {cout << "D()" << endl;}
~D() {cout << "~D()" << endl;}
protected:
int d;
};
class B: virtual public D
{
public:
B() {cout << "B()" << endl;}
~B() {cout << "~B()" << endl;}
protected:
int b;
};
class A: virtual public D
{
public:
A() {cout << "A()" << endl;}
~A() {cout << "~A()" << endl;}
protected:
int a;
};
class C: public B, public A
{
public:
C() {cout << "C()" << endl;}
~C() {cout << "~C()" << endl;}
protected:
int c;
};
int main() {
C c; // 构造函数,DBDAC,加了virtual后是DBAC
cout << sizeof(c) << endl; // 20,加了virtual后是40
// 析构函数,CADBD,加了virtual后是CABD
return 0;
}
构造函数与析构函数的调用
1、构造派生类对象:
a.调用派生类的构造函数;
b.在派生类构造函数初始化列表处调用基类的构造函数;
c.完成基类构造函数;
d.返回并完成派生类构造函数剩下部分
2、析构派生类对象:
a.调用派生类的析构函数;
b.执行派生类析构函数的函数体;
c.调用基类的析构函数;
d.执行基类析构函数函数体后返回