继承
继承是面向对象复用的重要手段
通过继承定义一个类,继承是类型之间的建模关系,共享共有的东西,实现各自本质不同的东西
三种继承关系:
public(公有继承),protected(保护继承),private(私有继承)
三种继承关系下基类成员在派生类的访问关系变化
总结:
1. 基类的私有成员在派生类中是不能被访问的,如果一些基类成员不想被基类对象直接访问,但在派生类中可以访问,则可以定义
为保护成员.保护成员限定符是因继承才出现的
2. public继承是一个接口继承,保持is-a原则,每个父类可用的成员子类也可用,每个子类对象也是一个父类对象
3. protected/private继承是一个实现继承,是has-a原则,基类的部分成员并未完全成为子类接口的一部分,所以在绝大多数的场
景下使用的是公有继承
4. 无论哪种继承方式,在派生类内部都可以访问基类的公有成员和保护成员,但基类的私有成员虽存在但在派生类中不可见(不能
访问)
5. 使用关键字class时的默认继承方式是private,关键字struct的默认继承方式是public
继承与转换
赋值兼容规则--public继承
1. 子类对象可以赋值给父类对象(切片/切割),无隐式类型转换的发生
切片只可以public继承
2. 父类对象不能赋值给子类对象
3. 父类的指针/引用可以指向子类对象
4. 子类的指针/引用需要通过强制类型转换才可以指向父类对象
#include <iostream>
using namespace std;
class Person
{
public:
void Display()
{
cout << _name << endl;
}
protected:
string _name;
};
class Student :public Person
{
public:
int _num;
};
int main()
{
Person p;
Student s;
p = s;
//s = p;
Person* p = &s;
Person& p = s;
Student* s = (Student*)&p;
Student& s = (Student&)p;
system("pause");
return 0;
}
隐藏(重定义)
在继承体系中基类和派生类都有独立的作用域
子类和父类中含有同名变量,函数名相同的函数(无论参数是否相同),子类成员将屏蔽对父类成员的直接访问.在子类成员函数中,
可以使用 基类::基类成员 访问
在继承体系中最好不要定义同名的成员
#include <iostream>
using namespace std;
class Person
{
public:
void f()
{
cout << "Person::f()" << endl;
}
void f1()
{
cout << "Person::f1()" << endl;
}
};
class Student :public Person
{
public:
void f()
{
cout << "Student::f()" << endl;
}
void f1(int i)
{
cout << "Student::f1()" << endl;
}
void f2()
{
cout << _num << endl;
cout << "Student::f2()" << endl;
}
void f3()
{
_num = 3;
cout << "Student::f3()" << endl;
}
void f4()
{
f();
cout << "Student::f4()" << endl;
}
private:
int _num;
};
int main()
{
Student* p = NULL;
p->f();//可以编过,输出Student::f()
p->f1();//两个f1()构成隐藏,若不传参,则编不过(调用Student中的f1())
p->f2();//可以编过,程序会崩溃 this指针为空,解引用会崩.若不解引用,则不会崩
p->f3();//可以编过,程序会崩溃
p->f4();//可以编过,输出Student::f4() 成员函数存放在公共代码段,不需要使用指针寻找函数
system("pause");
return 0;
}
在同一个作用域中才可能发生重载
派生类的默认成员函数
在继承关系里面,若派生类中没有显示定义这六个成员函数,编译系统则会默认合成这六个默认的成员函数
#include <iostream>
using namespace std;
class Person
{
public:
Person(const char* name = "xxx")
:_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(int num = 0)
:Person("sss")
,_num(num)
{}
Student(const Student& s)
:Person(s)
, _num(s._num)
{}
Student& operator=(const Student& s)
{
if (this != &s)
{
Person::operator=(s);
_num = s._num;
}
return *this;
}
~Student()
{
//Person::~Person();
}
protected:
int _num;
};
int main()
{
Student s1;
Student s2(s1);
Student s3;
s1 = s3;
Person p;
s1.~Student();
p.~Person();
system("pause");
return 0;
}
若想在子类中不初始化父类成员,则父类的构造函数为缺省构造函数
调用析构函数时,~Person()和~Student()构成隐藏,编译系统会将这两个函数变成同名函数
子类在调用析构函数后会自动调用父类的析构函数,所以子类的析构函数中不需要调用父类的析构函数
构造函数: 先父类,后子类
析构函数: 先子类,后父类
构造函数不可以显示调用,析构函数可以显示调用
*死循环不会造成栈溢出,若递归无出口,则会造成栈溢出
*实现一个不能被继承的类
将父类的构造函数写成私有的.因为父类私有的构造函数在子类中不可见而且子类的构造函数要调用父类的构造函数进行合成
class AA
{
private:
AA()
{}
public:
static AA* GetAAObj()
{
return new AA;
}
static AA DetAAObj()
{
return AA();
}
protected:
int _aa;
};
静态成员函数不需要对象调用
*实现一个只能在栈/堆上生成对象的类
class BB
{
public:
//栈上
//static BB GetBBObj()
//{
// return BB();
//}
//堆上
//static BB& GetBBObj()
//{
// return *(new BB);
//}
//
static BB* GetBBObj(int b)
{
return new BB(b);
}
private:
//类的防拷贝
BB(const BB& bb);
BB(int b = 0)
:_b(b)
{}
int _b;
};
int main()
{
//BB bb;
//BB* p = new BB;//operator new + 构造函数
//BB bb = BB::GetBBObj();//栈
//BB* p = BB::GetBBObj();//堆
//BB bb(*p);//栈 将拷贝构造声明,并且设置为私有的
system("pause");
return 0;
}
//实现一个只能在堆上生成对象的类(不推荐)
class BB
{
private:
void* operator new(size_t size);
void operator delete(void* p);
int _b;
};
BB b;//在静态区上可以生成对象
int main()
{
BB* p = new BB;
BB b;
return 0;
}
单继承&多重继承
单继承: 一个子类只有一个直接父类
多继承: 一个子类有两个或以上直接父类
*菱形继承
D的对象中有两份A成员
菱形继承存在二义性和数据冗余的问题
//显示指定访问哪个父类的成员
void Test()
{
D d;
d.B::_a = 10;
d.C::_a = 20;
}
虚继承解决了在菱形继承体系里面子类对象包含多份父类对象的数据冗余和浪费空间的问题
虚继承
#include <iostream>
using namespace std;
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()
{
cout << sizeof(D) << endl;//24 若不写virtual,则为20
system("pause");
return 0;
}
*虚继承是如何解决数据冗余和二义性?
内存对象模型
为什么虚继承中要存放虚基表指针?
多态
虚函数
类的成员函数前面加virtual关键字
虚函数重写
当在子类定义了一个与父类完全相同的虚函数时,称子类的这个虚函数重写(覆盖)了父类的这个虚函数
不构成多态的常规调用 -- 跟类型有关
构成多态 -- 跟对象有关
1.调用父类指针或引用
2.调用的函数必须是虚函数的重写
class Person
{
public :
virtual Person& BuyTickets()
{
cout << "买票-全价" << endl;
return *this;
}
protected :
string _name;
};
class Student :public Person
{
public :
virtual Student& BuyTickets()
{
cout << "买票-半价 " << endl;
return *this;
}
protected :
int _num;
};
//virtual void f();
void Fun(Person* p)
{
p->BuyTickets();
}
void Fun(Person& p)
{
p.BuyTickets();
}
int main()
{
Person p;
Student s;
Fun(p);//买票-全价
Fun(s);//买票-半价
system("pause");
return 0;
}
总结:
1. 派生类重写基类的虚函数实现多态,要求函数名,参数列表,返回值完全相同(协变除外)
协变: 返回值可以不同,但必须是指针或引用
若派生类重写基类的虚函数,则可以不加virtual
2. 基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性
3. 只有类的成员函数才能定义为虚函数,静态成员函数不能定义为虚函数
4. 如果在类外定义虚函数,只能在声明函数时加virtual,类外定义函数时不能加virtual
5. 构造函数不能定义为虚函数,operator=最好不要定义为虚函数
6. 不要在构造函数和析构函数中调用虚函数,因为对象不完整,可能会发生未定义行为
7. 基类的析构函数最好写为虚函数
纯虚函数
在成员函数的形参后面写上=0的函数
包含纯虚函数的类叫做抽象类(接口类),抽象类不能实例化出对象.纯虚函数在派生类中重新定义以后,派生类才能实例化出对象
class Person
{
public:
virtual void Display() = 0;
protected:
string _name;
};
class Student :public Person
{
public:
virtual void Display()
{
cout << "Student" << endl;
}
};
class Teacher :public Person
{
public:
virtual void Display()
{
cout << "Teacher" << endl;
}
};
int main()
{
//Person p;//不能实例化
Person* p = new Teacher;
p->Display();//Teacher
p = new Student;
p->Display();//Student
system("pause");
return 0;
}
友元与继承
友元关系不能继承,基类友元不能访问子类私有和保护成员
继承与静态成员
基类定义了一个static成员,则整个继承体系中此成员只有一个.无论有多少个派生类,都只有一个static成员实例
class Person
{
public:
Person()
{
++_count;
}
protected:
string _name;
public :
static int _count;
};
int Person::_count = 0;
class Student :public Person
{
protected:
int _stuNum;
};
class Graduate :public Student
{
protected:
string _seminarCourse;
};
void Test()
{
Person s1;
Student s2;
Student s3;
Graduate s4;
cout << Person::_count << endl;//4
Student::_count = 0;
cout << Person::_count << endl;//0
}
int main()
{
Test();
system("pause");
return 0;
}