目录
1、继承的概念与定义
概念
继承是面向对象程序设计中使代码复用的最重要的手段。它可以在保持原有类的特性上增加新的功能,通过这种方式产生的类,称为派生类。继承体现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。之前我们用到的复用大多是函数层面的,现在继承体现的就是类层面的复用。
class Person
{
public:
void Print()
{
cout << "name:" << _name << "age:" << _age << endl;
}
protected:
string _name = "张三"; // 姓名
int _age = 18; // 年龄
};
class Student : public Person
{
public:
//通过构造函数初始化时,子类只需要初始化自己的那一部分
//编译器会自动调用父类默认构造函数初始化继承的那一部分
Student(int id)
{
_id = id;
}
private:
int _id; // 学号
};
int main()
{
Student s(111);
s.Print();
return 0;
}
定义
类之间的继承有public(公有继承)、protected(保护继承)、private(私有继承)三种。
例如上面的例子就是公有继承,Person被称为基类/父类,Student被称为派生类/子类。
又因为类里面有三种访问权限,所以最终子类继承基类访问方式的变化有下面9种
类成员/继承方式 | public继承 | protected继承 | private继承 |
基类的public成员 | 派生类的public成员 | 派生类的protected成员 | 派生类的private成员 |
基类的protected成员 | 派生类的protected成员 | 派生类的protected成员 | 派生类的private成员 |
基类的private成员 | 在派生类中不可见 | 在派生类中不可见 | 在派生类中不可见 |
总结:
1、基类的private成员在派生类中,无论用什么继承方式,派生类都无法访问基类的private成员。上面写的不可见并不代表派生类没有继承,派生类其实是继承了的,但是语法上限制派生类对象不管在类里面还是类外面都不能访问到这些成员。
2、基类的private成员由于在派生类中无法访问,所以如果想让派生类访问到成员,而且不能在类外被直接访问,就可以设置成protected。
3、在实际应用中一般都是使用public继承,很少使用protected和private继承,因为protected/private继承下来的成员都只能在派生类里面使用,维护性不强。
2、基类和派生类对象赋值转换
1、派生类对象可以赋值给基类的对象 / 基类的指针 / 基类的引用。这种方式叫做切片或者切割。意思就是把派生类中继承基类的那一部分切割赋值给基类。
2、基类对象不能给派生类对象赋值。
3、基类的指针可以通过强制类型转换给派生类的指针,但是基类的指针必须指向派生类对象,而且基类必须是多态的,才可以通过C++11新增关键字dynamic_cast来进行转换。
class Person
{
protected:
string _name; // 姓名
int _age; // 年龄
};
class Student : public Person
{
public:
int _ID; // 学号
};
int main()
{
Person p;
Student s;
//子类对象给父类指针赋值
Person* ptr = &s;
//子类对象给父类引用赋值
Person& ref = s;
return 0;
}
3、继承中的作用域
1、在继承中基类和派生类都有自己独立的作用域,互不干扰。
2、如果基类和派生类中有同名成员,派生类会屏蔽基类的同名成员,这种情况叫做隐藏,也叫做重定义。如果要访问被隐藏的成员,可以通过 基类::基类成员 的方式来访问。
3、对于继承中的同名函数,只要函数名相同就会构成隐藏。
4、最好在继承中不要出现同名成员。
class Person
{
public:
void Print()
{
cout << "Person::Print()" << endl;
}
int s;
protected:
string _name = "张三";
int _ID = 111;
};
class Student : public Person
{
public:
void Print()
{
//如果基类成员是protected,继承方式是public或者protected
//那么只能通过类内访问
Person::Print();
cout << " 姓名:" << _name << endl;
cout << " ID:" << Person::_ID << endl;
cout << " 学号:" << _ID << endl;
}
private:
int _ID = 999;
};
int main()
{
Student s;
s.Print();
//如果基类成员是public,而且继承方式是public
//那么可以通过这种方式直接访问基类成员
s.Person::Print();
return 0;
}
4、派生类的默认成员函数
默认成员函数的意思就是我们不写,编译器会自动给我们生成一个,派生类中的默认成员函数是怎么生成的?
1、派生类的构造函数必须调用基类的构造函数先初始化基类的那一部分,如果基类没有默认的构造函数(不需要参数就可以调用的构造函数就叫做默认构造函数),那么派生类就必须在自己的构造函数中显示调用基类的构造函数。
2、派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
3、派生类的operator=必须调用基类的operator=完成基类的复制。
4、派生类的析构函数会在清理完派生类自己的成员后自动调用基类的析构函数完成基类成员的清理。
5、派生类对象的初始化会先调用基类的构造函数在调用自己的。
6、派生类的对象的清理会先调用自己的析构函数再去调用基类的。
5、继承与友元
友元关系不能继承,基类友元不能访问子类私有和保护成员。
//由于友元函数Display在Person里的声明中有Student类对象的引用
//但是编译器在前面没有找到Student类,所以必须先声明一下
class Student;
class Person
{
public:
friend void Display(const Person& p, const Student& s);
protected:
string _name = "李四";
};
class Student : public Person
{
public:
string _name = "张三";
protected:
int _ID;
};
void Display(const Person& p, const Student& s)
{
cout << p._name << endl;
//只能访问子类的公有成员
cout << s._name << endl;
//cout << s._ID << endl;//err
}
int main()
{
Person p;
Student s;
Display(p, s);
return 0;
}
6、继承与静态成员
如果基类中定义了一个static静态成员,那么继承中就只能存在一个这样的成员,且子类中成员不能与该成员同名。
class Person
{
protected:
static string _name;
int _ID = 111;
};
//静态成员类外初始化
string Person::_name = "张三";
class Student : public Person
{
public:
void Print()
{
cout << Person::_name << endl;
//cout << _name << endl;
}
private:
//与基类静态成员同名,错误
//string _name = "李四";//err
};
int main()
{
Student s;
s.Print();
return 0;
}
7、菱形继承
出现的问题
单继承:一个子类只有一个父类
多继承:一个子类有多个父类
菱形继承:是多继承的一种特殊情况
假设A中有一个成员变量_name,此时B、C都继承了A,那么B、C也有了成员变量_name,那么D继承B和C,就会有两份_name,此时就造成了代码冗余和二义性的问题。
解决方法
通过虚拟继承可以解决菱形继承中的代码冗余和二义性的问题。
class Person
{
protected:
string _name;//姓名
int _ID;//身份证号
};
class Student : virtual public Person
{
protected:
int _stuNum;//学生学号
};
class Teacher : virtual public Person
{
protected:
int _teaNum;//教工编号
};
class Assistant :public Student, public Teacher
{
protected:
string _Coursename;//课程名
};
下面通过另一段代码看看虚继承在内存中是怎样的
class A
{
public:
int _a;
};
class B : virtual public A
{
public:
int _b;
};
class C : virtual public A
{
public:
int _c;
};
class D : public B, public C
{
public:
int _d;
};
int main()
{
D d;//创建一个D类对象
d.B::_a = 1;//将d的父类B的_a设置为1
d.C::_a = 2;//将d的父类C的_a设置为1
d._b = 3;//将d继承的父类成员变量_b设置为3
d._c = 4;//将d继承的父类成员变量_c设置为4
d._d = 5;//将d自己的成员变量_d设置为5
return 0;
}
通常使用中非常不建议写菱形继承,能不用就不用。
8、继承和组合
1、继承是is-a的关系,意思就是派生类对象都有一个基类对象,例如:学生类和人这个类。
2、组合是has-a的关系,举例:汽车的组装,汽车有很多部件,像这种情况就可以使用组合。
3、继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关 系很强,耦合度高。
4、对 象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用,因为对象的内部细节是不可见的。
5、能用组合就不要去使用继承,因为组合符合高内聚、低耦合的编程思想。