概述
概念:在保持原有类的特性的基础上进行扩展,增加功能,产生新的类;
原有类被称为父类或者基类;新的类成为子类或者派生类、
目的:实现代码复用
特点:继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程,继承是类设计层次的复用。
定义格式
继承方式和访问限定符
继承关系:
public继承----公有继承
protected继承----保护继承
private继承----私有继承
访问限定符:public,protected,private
注意:继承方式是发生继承时在类外生效
,而访问限定符是在类内生效
发生继承后的访问方式变化
类成员/继承方式 | public继承 | protected继承 | private继承 |
---|---|---|---|
基类的public成员 | 派生类的public成员 | 派生类的protected成员 | 派生类的private成员 |
基类的protected成员 | 派生类的protected成员 | 派生类的protected成员 | 派生类的private成员 |
基类的private成员 | 在派生类中不可见 | 在派生类中不可见 | 在派生类中不可见 |
总结:
- 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
- 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出
保护成员限定符是因继承才出现的
。- 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。
基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。
- 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
- 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。
基类和派生类对象赋值转换
1、派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用
。这种方法叫切片
或者切割。就是把派生类中从父类继承的那部分值
切出来赋值给父类。
2、基类对象不能赋值给派生类对象
3、基类的指针可以通过强制类型转换赋值给派生类的指针。但是不建议使用这种方法进行赋值,因为强制类型转换有可能造成访问越界
。
代码示例:
#include<iostream>
using namespace std;
class Person{
private:
int age=1;
long ID=610523;
string name="张三";
string sex="男";
};
class Student:public Person{
private:
int stu_no;
int class_no;
};
void test()
{
Person p;
Student s;
p = s; //切片
//s = p; //基类对象不能赋值给派生类对象
Person* pp;
pp = &p;
Student* ss;
ss = (Student*)pp; //基类指针强制类型转换赋值给派生类指针
}
int main()
{
test();
return 0;
}
继承中的作用域
1、基类的派生类都有各自独立的作用域。
2、基类和派生类当中有同名成员
时,派生类成员将屏蔽基类对该成员的访问,这种情况叫隐藏,也叫重定义;在子类成员函数中,可以加基类::基类成员
显式访问。
#include<iostream>
using namespace std;
class A {
public:
void func()
{
cout << "A::func()" << endl;
}
private:
int _a=1;
};
class B :public A {
public:
void func()
{
cout << "B::func()" << endl;
}
void Print()
{
cout << _a << endl;
}
private:
int _a=100;
};
int main()
{
B b;
b.func();
b.Print();
return 0;
}
派生类的默认成员函数
构造函数:
派生类的构造函数必须调用基类的构造函数来初始化基类的那部分成员:
如果基类有默认的构造函数,那么编译器会在生成派生类对象时自动调用基类的构造函数;如果基类没有构造函数,则需要在派生类的构造函数中使用初始化列表时显式调用基类的一个构造函数。
基类有默认构造函数时:系统自动调用
基类没有构造函数时:
增加了派生类显式调用基类构造函数后:
拷贝构造函数:
派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
赋值运算符
派生类的operator=必须要调用基类的operator=完成基类的复制。
#include<iostream>
using namespace std;
class Person {
public:
Person(int i=1,int a=1):_id(i),_age(a)
{
cout << "Person()" << endl;
}
~Person()
{
cout << "~Person()" << endl;
}
Person& operator=(const Person& a)
{
if (this != &a)
{
_id = a._id;
_age = a._age;
}
cout << "Person operator=(const Person&)" << endl;
return *this;
}
private:
int _id;
int _age;
};
class Student :public Person{
public:
Student(int s = 01) :s_no(s)
{
cout << "Student()" << endl;
}
~Student()
{
cout << "~Student()" << endl;
}
private:
int s_no;
};
int main()
{
Student s1(1);
Student s2(2);
s1 = s2;
return 0;
}
注意:如果在子类的operator=函数中将基类的那一部分数据也赋值过去,系统就不会再调用基类的operator=,除非显式调用
析构函数:
派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。
因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
注意:基类的析构函数会在派生类对象销毁时自动调用,则不需要在派生类中显式调用。
所有的析构函数在底层实现都有一个共同的名字,所以即使在派生类中调用基类的析构函数也会发生同名隐藏。
继承与友缘
友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。
继承与静态成员
基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。
无论派生出多少个子类,都只有一个static成员实例 。
#include<iostream>
using namespace std;
class A {
public:
A()
{
++_a;
}
static int _a;
};
int A::_a = 0;
class B :public A {
};
int main()
{
A a;
B b;
cout << a._a << endl;
cout << b._a << endl;
cout << &a._a << endl;
cout << &b._a << endl;
return 0;
}
观察运行结果可知:类A 和 类B中的数据成员 _a 其实只有唯一 一份。