继承
继承是面向对象程序设计的核心思想之一。通过继承,我们可以大大提升代码的可复用性。
在面向对象程序设计中,类是对象的抽象,比如说我们设计一个Person类,这个类有姓名,年龄几个属性,用Person这个类去生成对象,就有了你,我,不同的人。你、我就是具体的人(对象)。
了解了面向对象的程序设计思想,就能更好地理解继承的概念。如果我们还需要一个Student类,Student有姓名,年龄,成绩几个属性。我们发现Student类与Person类都有年龄,姓名的属性。我们都知道Student一定是一个Person,继承就是用来表达这种“是”的关系。
class Person{
private:
string name;
int age;
public:
Person(string n,int a): name(n),age(a){};
Person(const Person &person){
name = person.name;
age = person.age;
}
~Person(){};
const string &getName() const {
return name;
}
void setName(const string &name) {
Person::name = name;
}
int getAge() const {
return age;
}
void setAge(int age) {
Person::age = age;
}
};
class Student:public Person{
private:
double grade;
public:
Student(const string &n, int a, double grade) : Person(n, a), grade(grade) {}
Student( const Student &student) : Person(student.getName(),student.getAge()) {
grade = student.grade;
}
double getGrade() const {
return grade;
}
void setGrade(double grade) {
Student::grade = grade;
}
};
- 定义继承的语法是:
class 类名 : 继承方式 基类名1,基类名2…
这里的继承方式只需要掌握public就可以。 - 当派生类生成对象时需要先调用基类的构造函数,在对象消亡时,会先调用派生类的析构函数。
- 派生类可以直接访问基类中的protected成员
- C++允许多重继承,即一个类可以继承自多个类
复写
我们给Person类加上一个say方法
void say(){
cout<<"I am a Person"<<endl;
}
那么此时如果在Student类中调用say方法,那么也会打印“I am a Person”,如果我们希望Student类能有自己独特的行为,我们就需要重载say方法,可以在Student类中加上以下代码
void say(){
cout<<"I am a Student"<<endl;
}
在用Student类的对象调用say方法时,编译器就会自动选择我们加入的特殊方法。
复写方法时,返回值类型,函数名,参数列表必须完全相同。
函数重载
如果同一作用域内的几个函数名字相同但形参列表不同,我们称之为重载函数。
mian函数不能重载 编译器根据调用语句当中的实参个数和类型判断应该调用哪个函数(不能通过返回值类型进行区分)
向下转型
为了更好地说明,我们再引入一个Worker类
class Worker : public Person{
private:
double salary;
public:
Worker(const string &n, int a, double salary) : Person(n, a), salary(salary) {}
double getSalary() const {
return salary;
}
void setSalary(double salary) {
Worker::salary = salary;
}
void say()
{
cout<<"I am a worker"<<endl;
}
};
当我们要办理身份证时,不需要考虑这个Person是Student还是Worker,只需要使用它们作为Person的特性。
这时我们就可以用到向下转型,通俗的说就是可以用基类指针来操作派生类对象,也就是可以直接把Student和Worker的对象直接当做Person对象来用。
Person *person;
Student student("Bob",18,56.4);
person = &student;
person->getName();
Worker worker("Jerry",40,2000);
person = &worker;
person->getName();
多态
还是以上述代码为例,当我们把派生类对象当做基类对象使用时,在调用其say方法时,调用的是基类的方法还是派生类中的方法呢?
Person *person;
Student student("Bob",18,56.4);
person = &student;
cout<<person->getName()<<endl;
person->say();
结果为:
Bob
I am a Person
显然,调用了基类的say方法,如果我们希望它还能调用派生类的say方法呢?
这就要用到虚函数,虚函数是C++多态的核心。我们只需要在基类的方法前添加上关键字virtual即可。
virtual void say(){
cout<<"I am a Person"<<endl;
}
再次执行上述代码,我们发现结果变成了
Bob
I am a Student
虚函数实现原理
当我们在函数定义前加上virtual关键字时,系统会自动为这个类的每一个对象建立一张虚函数表,用基类指针访问对象时,会先在虚函数表中找到对应的函数。(原理较复杂,了解即可)
多态的使用
在C++中,只有通过指针或者引用调用函数,该函数才会参与多态。
Person person[2];
Student student("Bob",18,56.4);
Worker worker("Jerry",40,2000);
person[0] = student;
person[1] = worker;
person[0].say();
person[1].say();
结果:
I am a Person
I am a Person
纯虚函数和抽象基类
如果我们再改变一下Person类中的say函数
virtual void say() = 0;
这时的say函数就是一个纯虚函数,纯虚函数不能在类中给出函数的具体实现(可以在派生类或类外给出实现)。
只要类中有一个函数是纯虚的,那么这个类就不能直接生成对象,代码Person person(“Kally”,10);就会报错。
但是依然可以生成对应的指针,代码 Person *person;
依然可以执行,所以抽象基类依然可以参与多态。