继承
继承是面向对象的复用手段,通过继承定义一个类,继承是类之间关系建模,共享共有的东西,实现各自本质不同的东西。
三种继承关系:public/protected/private基类成员在派生类的访问关系的变化如下
实现最基本的继承如下:
//父类
class Person
{
public:
Person(const char* name="")
:_name(name)
{}
void Print()
{
cout << _name << endl;
}
protected:
string _name;
};
//派生类
class Student : public Person
{
protected:
int _num;
};
注意:
- 基类的私有成员在派生类中不能访问,如果想访问定义为protected,由此可以看出,保护成员限定符因为继承出现的。
- public是一个接口继承,保持is-a原则,每个父类可用的成员(成员函数及成员变量)对子类也可用,每个子类对象也是一个父类对象。
- protected/private是一个实现继承,保持has-a原则,基类的部分成员并非成为子类接口的一部分。
- 不可见为存在但不能访问
- class默认的继承方式为private,struct默认的继承方式为public
继承与转换–赋值兼容规则(public继承)
- 子类对象可赋值给父类对象(切片/切割,编译器天然支持,不是隐式类型转换)
- 父类对象不可赋值给子类对象
- 父类的指针/引用可指向子类对象
- 子类的指针不能指向父类对象(强转可以)
代码验证为:
class Person
{
public:
void Print()
{
cout << _name << endl;
}
protected:
string _name;
};
class Student : public Person
{
public:
int _num;
};
int main()
{
Person p;
Student s;
//子类对象赋值给父类
p = s;
//父类对象赋值给子类
//s = p;
//父类的指针和引用可指向子类对象
Person *p1 = &s;
Person &r1 = s;
//子类指针与引用指向父类
Student *p2 = (Student*)&p;
Student &r2 =(Student&)p;
system("pause");
return 0;
}
隐藏(重定义)
定义:子类与父类中有同名成员,子类成员将屏蔽父类对成员的直接访问,即父类被隐藏。在子类中成员函数中,可通过指定作用域(基类:基类成员)访问
class Base
{
public:
virtual void f(float x)
{
cout << "Base::f(float) " << x << endl;
}
void g(float x)
{
cout << "Base::g(float) " << x << endl;
}
void h(float x)
{
cout << "Base::h(float) " << x << endl;
}
};
class Derived : public Base
{
public:
virtual void f(float x)
{
cout << "Derived::f(float) " << x << endl;
}
void g(int x)
{
cout << "Derived::g(int) " << x << endl;
}
void h(float x)
{
cout << "Derived::h(float) " << x << endl;
}
};
void Test()
{
Derived d;
Base *pb = &d;
Derived *pd = &d;
// Good : behavior depends solely on type of the object
pb->f(3.14f); // Derived::f(float) 3.14
pd->f(3.14f); // Derived::f(float) 3.14
// Bad : behavior depends on type of the pointer
pb->g(3.14f); // Base::g(float) 3.14
pd->g(3.14f); // Derived::g(int) 3 (surprise!)
// Bad : behavior depends on type of the pointer
pb->h(3.14f); // Base::h(float) 3.14 (surprise!)
pd->h(3.14f); // Derived::h(float) 3.14
}
接下来做道题吧,复习一下隐藏
class AA
{
public:
void f()
{
cout<<"AA::f()"<<endl;
}
};
class BB : public AA
{
public:
void f(int a)
{
cout<<"BB::f()"<<endl;
}
};
int main()
{
AA aa;
BB bb;
aa.f();
bb.f();//bb.AA::f();正确
/*A. BB里面有两个f函数,并且构成重载
B. BB里面有两个f函数,并且构成隐藏
C.上面代码编译器不过
D.上面代码可以编译通过,输出AA::f().*/
正确答案是:B C
解释:重载是两个成员函数在同一个类中,故A错;
隐藏是两个不同类的成员函数同名。故B对
父类成员函数被隐藏,故C对,D错
}
派生类的6个默认成员函数
若在派生类中没有显示定义,则系统默认合成这6个默认成员函数。
代码实现:
#include <iostream>
using namespace std;
#include <windows.h>
#include <string>
//默认成员函数
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)
{
if (this != &p)
{
_name = p._name;
}
return *this;
cout << "Person& operator=(const Person& p)" << endl;
}
//析构函数
~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)
{
if (this != &s)
{
Person::operator=(s);
_num = s._num;
}
return *this;
cout << "Student& operator=(const Student& s)" << endl;
}
//析构
~Student()
{
cout << "~Student()" << endl;
}
private:
int _num;
};
int main()
{
Student s1("林在范",18);
Student s2(s1);
Student s3("JB", 21);
s1 = s3;
system("pause");
return 0;
}
结果如图
注意:
1. 合成版本:在初始化列表,父类构造初始化父类成员,子类构造初始化子类成员
2. 父类的构造函数不需要在子类显示写出,会自动调用父类的。析构函数经编译后在汇编层看会变为destructor。子类析构函数会隐藏父类。原因如下:
单继承与多继承
- 单继承- -一个子类只有一个直接父类时称这个继承关系为单继承
- 多继承- -一个子类有两个或以上直接父类时称这个继承关系为多继承
菱形继承:由多继承产生
菱形继承会存在二义性与数据冗余问题。
那么如何解决呢·?这就引出了新的问题 ,什么是虚继承?
虚继承:在共同继承的类前加上virtual关键字。
虚继承通过对象内存模型解决了二义性与数据冗余问题。
代码实现:
#include <iostream>
using namespace std;
#include <string>
#include <windows.h>
class Person
{
public:
int _id;//编号
};
class Student:virtual public Person
{
public:
int _num;//学号
};
class Teacher:virtual public Person
{
public:
int _tid;//教工号
};
class Assistant :public Student, public Teacher
{
public:
int _aid;//助理号
};
int main()
{
cout << sizeof(Assistant) << endl;//未加virtual之前为20,加了之后为24?
Assistant a;
a.Student::_id = 1;
a.Teacher::_id = 2;
a._num = 3;
a._tid = 4;
a._aid = 5;
system("pause");
return 0;
}
菱形继承的虚拟继承对象模型:
汇编层查看:
虚函数重写(覆盖)
当在子类的定义了一个与父类完全相同的虚函数时,则称子类的这个函数重写(也称覆盖)了父类的这个虚函数。
#include <iostream>
using namespace std;
#include <windows.h>
#include <string>
class Person
{
public:
virtual void BuyTickets()
{
cout << "买票-全价"<< endl;
}
protected :
string _name; // 姓名
};
class Student : public Person
{
public:
virtual void BuyTickets()
{
cout << " 买票-半价 "<< endl;
}
protected :
int _num; //学号
};
//void Fun(Person* p)
void Fun(Person& p)
{
p.BuyTickets();
}
int main()
{
Person p;
Student s;
Fun(p);
Fun(s);
system("pause");
return 0;
}
对象调用哪一函数?
- 与类型有关->常规情况
- 与指向对象有关->多态(条件:虚函数重写;对象指针或引用)
注意:
- 派生类重写基类的虚函数实现多态,要求函数名、参数列表、返回值完全相同。(协变除外,协变是指函数返回值不同,一个为父类指针,一个为子类指针)
- 基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性
- 只有类的成员函数才能定义为虚函数
- 静态成员函数不能定义为虚函数
- 如果在类外定义虚函数,只能在声明函数时加virtual,类外定义函数时不能加virtual
- 构造函数不能为虚函数,虽然可以将operator= 定义为虚函数,但是最好不要将operator = 定义为虚函数,因为容易使用时容易引起混淆。
- 不要在构造函数和析构函数里面调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会发生未定义的行为
最好把基类的析构函数声明为虚函数。(why ?另外析构函数比较特殊,因为派生类的析构函数跟基类的析构函数名称不一样,但是构成覆盖,这里是因为编译器做了特殊处理)
对第8条作解释,显示调用一次析构函数并查看其汇编代码
可以看到编译器对其做了处理。
接下来对比一下隐藏,重载,覆盖?