C++是OOP(Object Oriented Programming)语言,即面向对象编程语言,OOP的核心思想就是数据抽象(类的大设计),继承和动态绑定。
何为继承?
首先我们想到继承是什么?继承我们可以简单的理解为孩子继承了父母的基因,在拥有父母的某些特性的同时自己又有自己独立的特性。
在C++中,继承是类之间的继承,即某个类可以继承它的成员函数和成员变量,同时又有自己独立的成员函数和成员变量。
继承的格式定义如下:
class <派生类类名>:<继承方式><基类类名>
{
};
派生类即新生的类,相当于孩子,又称子类,基类即原有的类,相当于父亲,又称父类。
其中继承的方式主要有三种:
- public:将保留成员的原有的访问权限,即基类中的public成员仍然是public成员,protected成员仍然为protected成员,private仍为private,且派生类无权去访问。
- protected:将保护基类的所有成员,基类中的私有成员仍无权进行访问,但是protected和public成员将作为保护成员进行继承。
- private:将以私有方式继承基类中的所有成员。无论成员变量原有的访问权限是什么,在派生类中均为私有属性,但基类的私有成员派生类仍无权访问。
下面我没问将用表格进行详细的说明
- 如果一些基类成员不想被基类对象直接访问,但需要在派生类中能访问,就定义为保护成员,保护限定符是因继承才出现的。
- public继承是一个接口继承,保持is_a原则,每个父类可用的成员对子类也可用,因为每个子类对象也都是一个父类对象。
- protected/private继承是一个实现继承,基类的成员并未完全成为子类接口的一部分,是一个has_a的关系。所以在绝大多数的情况下我们都使用的是公有继承。
- 使用class关键字时默认的继承方式都是private,但是public使用继承方式时公有继承,不过我们在使用的时候都是一般显示的写出继承方式。
我们一般在实际使用的时候Base表示基类名称,Derive表示派生类名称。
继承与转换—-赋值兼容规则—public继承
1、子类对象可以赋值给父类对象(切割/切片)
2)父类对象不能赋值给子类对象
3)父类的指针/引用可以指向子类对象
4)子类的指针/引用不能指向父类对象(可以通过强制类型转换完成)
sing namespace std;
class Person
{
public:
void Display()
{
}
protected:
string _name;//姓名
};
class Student:public Person
{
public:
int _num;//学号
};
void test()
{
Person p;
Student s;
//1、子类对象可以赋值给父类对象(切割、切片)没有类型的转换,是纯天然的
p = s;
//2、父类对象不能赋值给子类对象
//s= p;
//3、父类对象的指针和引用可以指向子类对象
Person * p2 = &s;
Person& r1 = s;
//4、子类对象的指针和引用不能给父类,但是可以通过强制类型的转换就可以进行转换片
Student* p3 = (Student*)&p;
Student& r2 = (Student&)p;
//这里会发生什么变化?
程序崩溃
p2->_num = 10;
r2._num = 20;
}
继承体系中的作用域
1)在继承体系中基类和派生类都有自己的独立的作用域。
2)子类和父类中有同名成员,子类成员将屏蔽父类对成员的直接访问。(在子类成员函数中,可以使用基类::基类成员 访问)———隐藏)(重定义)
在这里需要注意的是成员变量、成员函数(针对成员函数来说,只要函数名相同,就会构成隐藏)
3)在实际使用的过程中我们一般不会定义同名的成员
class A
{
public:
int a = 10;
};
class B:public A
{
public:
int a= 20;
};
int main()
{
B d;
cout << d.a << endl;
cout << d.A::a << endl;
system("pause");
return 0;
}
最后的结果:
派生类的默认成员函数
在继承关系中,在派生类中如果没有显示定义这六个成员函数,编译系统会默认合成这六个默认的成员函数
如何实现一个不能继承的类
class A
{
private:
A()
{
}
};
在这里我们主要注意两个方面的东西
1)private和任何的方式进行组合的时候,最后的到的都是不可见的。
2)一般情况下,子类都要调用父类的初始化构造函数,当我们定义为空的时候,其中没有初始化的构造函数可以调
class Person
{
public:
Person(const char* name)
:_name(name)
{
cout << "Person()" << endl;
}
Person(const Person& p)
:_name(p._name)
{
cout << "Person(const Person& p)" << endl;
}
Person& operator = (const Person& p)
{
cout << "Person& opertor=(const Person& p)" << endl;
if (this != &p)
{
_name = p._name;
}
return *this;
}
~Person()
{
cout << "~person()" << endl;
}
protected:
string _name;//姓名
};
class Student:public Person
{
public:
Student(const char* name, int num)
:Person(name)
, _num(num)
{
cout << "Student()" << endl;
}
Student(const Student& s)
:Person(s)//在这里存在着切片
, _num(s._num)
{
cout << "Student(const Student& s)" << endl;
}
Student& operator=(const Student& s)
{
cout << "Student(const Student& s)" << endl;
if (this != &s)
{
Person::operator=(s);//在这里如果没指明类,
//就会将父类的赋值运算符重载函数进行隐藏
//就不会调用父类的,只会调用自己的赋值运算符函数,
_num = s._num;
}
return *this;
}
~Student()
{
cout << "~Student()" << endl;
}
private:
int _num;//学号
};
void test()
{
Student s1("jack", 18);
Student s2(s1);
Student s3("rose", 19);
s1 = s3;
}
运行的结果主要如下:
当子类在继承父类的时候回优先的调父类的函数,如果函数构成重定义(隐藏)的时,子类会自动的隐藏对父类成员的访问。
用下面的实例来演示三种继承关系下基类成员的各类型成员访问关系的变化。
class Base
{};
class Derive1:public Base//公有继承
{};
class Derive2:protected Base//保护继承
{};
class Derive3:private Base//私有继承
{};
//上面的代码中,Derive1,Derive2,Derive3都继承来自Base基类。区别就是三种的继承方式是不同的。
多继承
一个子类有两个及两个以上的父类时,称这个继承为多继承。
我们注意在进行多继承的时候,要是我们没有说明继承的方式,那么默认的继承方式就是private。
菱形继承
菱形继承存在二义性和数据冗余的问题。简单来说如下:
class Person
{
public:
string _name;//姓名
};
class Student :public Person
{
protected:
int _num;//学号
};
class Teacher :public Person
{
protected:
int _id;//工号
};
class Assistant:public Student,public Teacher
{
protected:
string _majorCourse;
};
void test()
{
//显示指定访问哪个父类成员
Assistant a;
a.Student::_name = "xxx";//一个人会有两个不同的身份
a.Teacher::_name = "yyy";//两个不同的名字
}
简单的来说我们定义的对象时多种角色,我们在识别的时候,就不知道它到底是属于其中的什么角色,显示指定访问哪个父类成员。
虚继承:解决菱形继承的二义性和数据冗余
1、虚继承解决了菱形继承体系当中子类对象包含了多种父类对象的数据冗余&浪费空间(两个子类同时都拥有父类的成员)的问题
2、虚继承解决了数据冗余问题也带来了性能上的损耗。
class A
{
public:
int a=1;
};
class B1:virtual public A
{
public:
int b1 = 2;
};
class B2 : virtual public A
{
public:
int b2 = 3;
};
class C : public B1, public B2
{
public:
int c = 4;
};
int main()
{
C c;
system("pause");
return 0;
}
虚基表也叫做偏移量表格,每个对象都存有一个虚基表指针,指向一个偏移量表格(虚基表),通过相对偏移量的大小就能找到基类成员。每个虚基表指针指向了都指向了基类成员,这样解决了数据二异性和冗余性。