目录
面向对象的三大特性:封装,继承,多态。
继承作为三大特性之一,是使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。继承呈现了面向对象程序设计的层次结构,继承是类设计层次的复用。
继承的定义
格式的定义:
//A类为基类
class A{
public:
int a;
};
// B 继承于 A
class B : public A {
public:
int b;
};
class B : public A 这句中 :其中B类为派生类,也叫子类;A类为基类,也叫父类;public为继承方式。
注意:继承后父类(A类)的成员(成员函数+成员变量)都会变成子类(B类)的一部分,B类也有自己的数据和方法。这里体现出了B类复用了A类的成员。B类对象可以访问到这些数据和方法,但也要有权限问题。
举个例子:
//Person类为基类
class Person{
public:
string name;
int age;
void Print()
{
cout << name << " " << age << endl;
}
};
//Student类继承了Person
class Student : public Person{
private:
int stuid: // 学号
};
//Teacher类继承了Person
class Teacher : public Person{
private:
int jobid;//教师职工号
};
Student类和Teacher类继承Person类,这两个类继承了Person类中的数据 ( name和age ) 和方法 ( Print() ) 。
通过复用,两个派生类都可以使用基类的数据和方法,不用再写声明相同的数据和方法,节省了代码量。
上面提到了继承的方式为public,其实还有private继承方式和protected继承方式。
下图为继承方式和访问限定符的关系图:
总结如下:
1、基类private成员在派生类中是不可见的。不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里还是类外都不能去访问它。
2、若基类成员不想在类外直接被访问,但想要在派生类中访问,就定义为protected。protected限定符是因继承才出现的。
3、class中默认为private继承,struct中默认为public继承。但最好显示的写出继承方式。
4、public继承最为常用,private/protected继承只能在类中访问,作用不大。在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承。这也是C++面向对象的较坑的一点。
基类对象和派生类对象的赋值转换
基类与派生类之间的赋值转换只能发生在公有继承中。
1、派生类赋值给基类
派生类对象可以赋值给 基类的对象/基类的指针/基类的引用。
这个行为我们可以把它叫做 切片,意思就是把派生类中基类的那部分切分开赋值给基类。如下图所示
class A
{
public:
int a;
int b;
int c;
};
class B : public A
{
public:
int d = 4; //B中继承了A的a b c
};
int main()
{
B b;
b.a = 1;
b.b = 2;
b.c = 3;
//下面的过程叫切片
//子类对象可以赋值给父类对象/指针/引用
A a1 = b;
A* a2 = &b;
A& a3 = b; // 注意a3不是b的别名,而是其中父类部分的别名
//a对象中的成员变量a b c分别等于1 2 3
return 0;
}
对象间的赋值,是将派生类从基类继承来的数据复制一份给基类对象。
指针间的赋值,是将基类的指针指向派生类的对象,但所指的范围只包括基类的数据。
引用间的赋值,引用在底层也是一个指针,所以原理指针赋值的原理基本相同。
2、基类 无法 赋值给派生类
b = a; //这是错的
3、基类的指针可以通过强制类型转换赋值给派生类的指针
基类的指针必须是指向派生类对象地址才是安全的。
//a:基类 b:派生类
A *a = &b;
B *b1 = (B*)a; //这样可以
//a:基类 b:派生类
A a1 = b //切片
A *a2 = &a1;
B *b1 = (B*)a2; //这种情况转换时虽然可以,但会存在越界访问的问题。
//只能访问到基类的数据,无法访问派生类数据,是乱码。
继承中的作用域
- 继承体系中,基类和派生类都有自己独立的作用域。
- 若子类和父类中有同名的成员,子类成员将屏蔽对父类中同名的成员的直接访问,这种行为叫隐藏,也叫重定义。若想访问父类同名成员,则要使用 基类::基类成员 的格式显示访问。
- 若是成员函数的隐藏,只需让函数名相同就可以。
- 但在继承体系中,最好不要定义同名的成员。
class A
{
public:
int x = 1;
int y = 2;
void fun()
{
cout << x << " " << y << endl; //打印结果为 1 2
}
};
class B : public A
{
public:
int x = 3; //B类中的成员x和A类中的成员X构成隐藏(重定义)
int z = 4;
void fun() //B类中的成员函数fun和A类中的成员函数fun构成隐藏(重定义),成员函数满足函数名相同就构成隐藏
{
cout << x << " " << z << endl; //打印结果为 3 4
cout << A::x << endl; //打印结果为 1 ,显示的调用父类的成员
}
};
派生类与基类中成员的关系
派生类与默认成员函数的关系
- 派生类的构造函数调用基类的构造函数来初始化继承字基类中的数据。如果基类中没有默认的构造函数,就在派生类构造的初始化列表阶段进行显示的调用。
- 派生类对象初始化先调用基类构造再调派生类构造。
- 派生类对象析构清理先调用派生类析构再调基类的析构。派生类的析构与基类的析构构成了隐藏(由于继承中多态的原因),析构函数不显示调用,由编译器自己调用。
- 派生类的拷贝构造调用基类的拷贝构造完成对基类数据的拷贝。
- 派生类的operator=调用基类的operator=完成对基类数据的赋值。
class A
{
protected:
int x;
public:
A(int _x = 1)
:x(_x)
{
cout << "A()" << endl;
}
A(const A& a)
:x(a.x)
{
cout << "A(const A&)" << endl;
}
A& operator=(const A& a)
{
cout << "operator=()" << endl;
if (this != &a)
{
x = a.x;
}
return *this;
}
~A()
{
cout << "~A()" << endl;
}
};
class B : public A
{
protected:
int y;
public:
B(int _x = 1, int _y = 2)
:A(_x) //先调基类构造
, y(_y)
{
cout << "B()" << endl;
}
B(const B& b)
:A(b) //先调拷贝基类构造
,y(b.y)
{
cout << "B(const B&)" << endl;
}
B& operator=(const B& b)
{
cout << "operator=()" << endl;
if (this != &b)
{
A::operator=(b);
y = b.y;
}
return *this;
}
~B() // 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员
{
cout << "~B()" << endl;
}
};
继承与友元的关系
友元关系不能继承(基类友元不能访问子类私有和保护成员)
继承与静态成员的关系
- 基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一 个static成员实例。
- 所有子类对静态成员的操作都只是在同一个静态成员之上。