文章目录
- 前言
- 一、菱形继承的类对父类的初始化
- 二、组合
- 三、 多态
- 1. 构成多态
- 2. 虚函数
- 3. 虚函数的重写
- 4. 虚函数重写的两个例外
- 1. 协变
- 2. 析构函数的重写
- 5. C++11 final 和 override
- 1. final
- 2. override
- 6. 设计不想被继承的类
- 7. 重载、覆盖(重写)、 隐藏(重定义)的对比
- 四、多态的原理
- 总结
前言
菱形继承的类对父类的初始化、组合、多态、多态的原理等的介绍
一、菱形继承的类对父类的初始化
#include<iostream>
#include <string>
using namespace std;
class A
{
public:
A(const char* A)
:_a(A)
{
cout << "class A" << endl;
}
string _a;
};
class B : virtual public A
{
public:
B(const char* A, const char* B)
:A(A)
,_b(B)
{
cout << "class B" << endl;
}
string _b;
};
class C : virtual public A
{
public:
C(const char* A, const char* C)
:A(A)
,_c(C)
{
cout << "class C" << endl;
}
string _c;
};
class D : public B, public C
{
public:
D(const char* A, const char* B, const char* C, const char* D)
: A(A)
, B(A, B)
, C(A, C)
, _d(D)
{
cout << "class D" << endl;
}
string _d;
};
int main()
{
D d("class A", "class B", "class C", "class D");
return 0;
}
结构为:
- 因为D类有如上的结构,所以A类会在D类中调用构造函数初始化,并且只调用一次,在D类中初始化B类和C类时,传入的A类不调用A类的构造函数初始化。
二、组合
- public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。
- 组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。
优先使用对象组合,而不是类继承。
#include <iostream>
using namespace std;
class A
{
protected:
int _a;
};
class B : public A
{
protected:
A _bb;
};
int main()
{
return 0;
}
三、 多态
1. 构成多态
构成多态需要两个条件:
- 必须通过基类的指针或者引用调用虚函数
- 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
// 构成多态
#include<iostream>
using namespace std;
class Person
{
public:
virtual void BuyTicket()
{
cout << "购票---全价" << endl;
}
};
class Student : public Person
{
public:
virtual void BuyTicket()
{
cout << "购票---半价" << endl;
}
};
//void fun(Person& p)
//{
// p.BuyTicket();
//}
void fun(Person* p)
{
p->BuyTicket();
}
int main()
{
Person p;
fun(&p);
Student s;
fun(&s);
return 0;
}
2. 虚函数
虚函数:即被virtual修饰的类成员函数称为虚函数。
class Person
{
public:
virtual void BuyTicket()
{
cout << "购票---全价" << endl;
}
};
3. 虚函数的重写
虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。
在重写基类的虚函数的时候,虽然派生类的虚函数不写virtual也可以构成重写, 但是不建议这样使用
#include<iostream>
using namespace std;
class Person
{
public:
virtual void BuyTicket()
{
cout << "购票---全价" << endl;
}
};
class Student : public Person
{
public:
virtual void BuyTicket()
{
cout << "购票---半价" << endl;
}
};
4. 虚函数重写的两个例外
1. 协变
基类与派生类虚函数返回值类型不同。
简单来讲就是 基类与派生类的虚函数的返回值构成父子类指针或引用关系。
#include <iostream>
using namespace std;
class A {};
class B :public A {};
class Person
{
public:
virtual A* BuyTicket()
{
cout << "购票---全价" << endl;
return new A;
}
};
class Student : public Person
{
public:
virtual B* BuyTicket()
{
cout << "购票---半价" << endl;
return new B;
}
};
void fun(Person& p)
{
p.BuyTicket();
}
int main()
{
Person p;
fun(p);
Student s;
fun(s);
return 0;
}
- 基类和派生类构成父子类指针的关系
2. 析构函数的重写
虽然基类和派生类的析构函数的函数名不同,但是编译器会将析构函数的函数名,都处理成destructor,因此可以构成重写。
一般情况下,应该将派生类的析构函数与基类析构函数构成函数重写,使下面的情况delete可以实现多态,保证指向的对象正确调用析构函数。
析构函数可以构成虚函数重写吗, 为什么要构成虚函数重写?
- 析构函数加virtual会构成析构函数,因为编译器会将析构函数的名字统一命名为destructor
- 构成虚函数重写是因为,我们new一个派生类对象的空间,但是用基类的类型指针接收
- 在析构这个对象时,只会进行普通调用,普通调用会按照当前类型, 则只会调用基类的析构函数
- 这种情况,我们希望是一个多态调用,按照指向的类型调用析构函数,就需要构成虚函数的重写。
#include <iostream>
using namespace std;
class Person
{
public:
virtual void BuyTicket()
{
cout << "购票---全价" << endl;
}
virtual ~Person()
{
cout << " ~Person() " << endl;
}
};
class Student : public Person
{
public:
virtual void BuyTicket()
{
cout << "购票---半价" << endl;
}
virtual ~Student()
{
cout << " ~Student() " << endl;
}
};
int main()
{
Person* p1 = new Person;
Person* p2 = new Student;
delete p1;
delete p2; // p2->destuctor() + operator delete(p)
// 这里我们期望是一个多态调用,而不是普通调用
p1 = nullptr;
p2 = nullptr;
return 0;
}
5. C++11 final 和 override
1. final
final 修饰的虚函数不能被重写
final修饰的类,不能被当做基类, 不能被继承
2. override
检查派生类中的虚函数与基类中虚函数是否构成重写,若不构成重写则报错。
#include <iostream>
using namespace std;
class Person
{
public:
virtual void BuyTicket()
{}
};
class Student : public Person
{
public:
// 检查派生类中的虚函数与基类中的虚函数是否构成重写
virtual void BuyTicket()override
{
cout << "购票---半价" << endl;
}
};
int main()
{
Person p;
return 0;
}
6. 设计不想被继承的类
将构造函数私有或者将析构函数私有
将构造函数私有
#include <iostream>
using namespace std;
class A
{
public:
static A CreateObj()
{
return A();
}
private:
A() {}
};
int main()
{
A::CreateObj();
return 0;
}
7. 重载、覆盖(重写)、 隐藏(重定义)的对比
四、多态的原理
普通调用在编译时地址就确定了
多态调用在程序运行时,到指向对象的虚函数表中找函数的地址
#include <iostream>
using namespace std;
class Person
{
public:
virtual void BuyTicket()
{
cout << "购票---全价" << endl;
}
int _a = 0;
};
class Student: public Person
{
public:
virtual void BuyTicket()
{
cout << "购票---半价" << endl;
}
int _b = 1;
};
// 普通调用
//void fun(Person p)
//{
// p.BuyTicket();
//}
// 多态调用
void fun(Person& p)
{
p.BuyTicket();
}
int main()
{
Person p;
Student s;
return 0;
}
总结
菱形继承的类对父类的初始化、组合、多态、多态的原理等的介绍