1 C++中的继承
- C++中的类的继承机制提供了较高层次的重用性。许多厂商提供了类库,类库由类声明和实现构成。类继承可以从已有的类中派生出新的类,派生类继承了基类的数据和方法。
- 我们可以在派生类中添加新的类数据或类方法,此时称为公有继承。如果在派生类中修改了基类类方法的行为,那么称为多态公有继承。
- 一般情况下,我们修改基类源码就可以实现派生类。C++的继承机制甚至不需要访问基类的源码就可以派生出派生类,只需要基类的头文件和编译后的代码即可。
1.1 公有继承
使用公有继承,可以达到如下的特性:
- 基类的公有成员成为派生类的公有成员,基类的私有部分也成为派生类的一部分,但只能通过基类的公有和保护方法访问。
- 派生类需要自己的构造函数,派生类不能访问基类的私有数据,所以派生类构造函数要调用基类构造函数来初始化基类的私有数据。基类对象应在程序进入派生类构造函数前被创建,用初始化列表实现。
- 基类指针或引用可以在不进行显式类型转换的情况下指向派生类对象,但基类指针或引用只能调用基类成员。
- 可以将基类对象初始化为派生类对象,可以将派生类对象赋给基类对象。
// tabtenn1.h
#ifndef TABTENN1_H_
#define TABTENN1_H_
#include <string>
using std::string;
class TableTennisPlayer //基类声明
{
private:
string firstname;
string lastname;
bool hasTable;
public:
TableTennisPlayer (const string & fn = "none",const string & ln = "none", bool ht = false);
void Name() const;
bool HasTable() const { return hasTable; };
void ResetTable(bool v) { hasTable = v; };
};
class RatedPlayer : public TableTennisPlayer //派生类声明,公有派生
{
private:
unsigned int rating;
public:
RatedPlayer(unsigned int r = 0, const string & fn = "none",
const string & ln = "none", bool ht = false);
RatedPlayer(unsigned int r, const TableTennisPlayer & tp);
unsigned int Rating() const { return rating; }
void ResetRating (unsigned int r) {rating = r;}
};
#endif
//tabtenn1.cpp
#include "tabtenn1.h"
#include <iostream>
TableTennisPlayer::TableTennisPlayer (const string & fn, const string & ln, bool ht) :
firstname(fn),lastname(ln), hasTable(ht) {}
void TableTennisPlayer::Name() const
{
std::cout << lastname << ", " << firstname;
}
RatedPlayer::RatedPlayer(unsigned int r, const string & fn,
const string & ln, bool ht) : TableTennisPlayer(fn, ln, ht)//在初始化列表显式调用基类构造函数
{
rating = r;
}
RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer & tp)
: TableTennisPlayer(tp), rating(r) //调用基类复制构造函数,未定义,默认隐式复制
{
}
1.2 多态公有继承
- 同一个方法的行为在基类和派生类中不同时,称为多态公有继承。
- 我们要在基类声明中将基类中的那些要在派生类中重新定义的方法声明为虚的virtual,最好在派生类中也同样用关键字virtual指明,实现代码中不用加virtual。这样程序使用动态联编的技术,将根据引用类型或指针类型选择相应的方法,而非根据对象类型。
- 基类的析构函数也要声明为虚的,这样能确保正确的析构顺序。 如果基类析构函数不是虚的,将只调用对应于指针类型的析构函数。基类析构函数是虚的,能够保证析构派生类对象时,先调用派生类析构函数,然后会自动调用基类析构函数。
- 派生类可以直接调用基类的公有方法,但当派生类重写该函数而导致函数同名时,注意在调用基类公有方法时加上作用域解析运算符。
- protected关键字用于对象的保护访问。protected和privated的区别只在派生类体现,派生类的成员可以直接访问基类的保护成员,但不能直接访问基类的私有成员。其他类不能访问基类的保护成员。一般我们将protected用于那些希望只被派生类访问的成员函数。
//student.h
#ifndef STUDENT_H
#define STUDENT_H
#include <QString>
class cl_person //基类
{
private:
QString m_name;
int m_age;
public:
cl_person(QString name = "no_name",int age = 18);
virtual ~cl_person() {} //基类析构函数声明为虚的
virtual QString getName() const {return m_name;} //派生类要重写,基类声明为虚的
int getAge() const {return m_age;}
};
class cl_student : public cl_person //公有派生
{
private:
QString m_school;
public:
cl_student(QString name = "no_name",int age = 18,QString school = "no_school");
~cl_student() {}
virtual QString getName() const; //派生类重写的基类函数,我们也一般把它声明为虚的
};
#endif // STUDENT_H
//student.cpp
#include "student.h"
cl_person::cl_person(QString name,int age)
{
m_name = name;
m_age = age;
}
cl_student::cl_student(QString name,int age,QString school) : cl_person(name,age) //在初始化列表显式调用基类构造函数
{
m_school = school;
}
QString cl_student::getName() const
{
return (m_school + " " + this->cl_person::getName()); //调用基类的公有函数,由于要重写而同名,故用作用域解析运算符
} //无重名发生时,直接调用即可
//main.cpp
#include <qDebug>
#include <student.h>
#include <iostream>
int main()
{
cl_person* p_cl_person[2]; //定义指向基类的指针,一个数组表示多种类型的对象,体现多态性
int a;
for(int i = 0;i < 2;i++)
{
std::cout << "enter 0 to creat cl_person,1 to creat cl_person" << std::endl;
std::cin >> a;
if(a == 0) //根据用户选择的不同创建不同类型的对象
p_cl_person[0] = new cl_person();
else if(a == 1)
p_cl_person[1] = new cl_student("dwp",24,"uestc");
else
{
std::cout << "please enter 0 or 1" << std::endl;
i--;
}
}
for(int i = 0;i < 2;i++)
{
qDebug() << p_cl_person[i]->getAge();
qDebug() << p_cl_person[i]->getName();
}
return 0;
}
1.3 抽象基类(ABC)
- 在C++中函数声明了就必须定义,否则会报错。C++中函数声明了就必须定义
- C++通过使用纯虚函数提供未实现的函数,纯虚函数声明的结尾处为=0。
virtual double Area() const = 0;
- 类声明中包含纯虚函数时,不能创建该类的对象,这个类为抽象基类。只有派生类重写了这个纯虚函数后,才能创建类对象。
- 纯虚函数也可以包含定义,但只要它是纯虚函数,就不能创建类对象。